Skip to content
Latchkey

Release Proposal Approval Tracker workflow (caddyserver/caddy)

The Release Proposal Approval Tracker workflow from caddyserver/caddy, explained and optimized by Latchkey.

C

CI health: C - fair

The optimized version below adds run de-duplication, job timeouts.

Source: caddyserver/caddy.github/workflows/auto-release-pr.ymlLicense Apache-2.0View source

What it does

This is the Release Proposal Approval Tracker workflow from the caddyserver/caddy repository, a real project running GitHub Actions. It is shown here with attribution under its Apache-2.0 license.

Below, Latchkey shows a faster, safer version produced by its optimization engine.

The workflow

workflow (.yml)
name: Release Proposal Approval Tracker

on:
  pull_request_review:
    types: [submitted, dismissed]
  pull_request:
    types: [labeled, unlabeled, synchronize, closed]

permissions:
  contents: read
  pull-requests: write
  issues: write

jobs:
  check-approvals:
    name: Track Maintainer Approvals
    runs-on: ubuntu-latest
    # Only run on PRs with release-proposal label
    if: contains(github.event.pull_request.labels.*.name, 'release-proposal') && github.event.pull_request.state == 'open'
    
    steps:
      - name: Check approvals and update PR
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
        env:
          MAINTAINER_LOGINS: ${{ secrets.MAINTAINER_LOGINS }}
        with:
          script: |
            const pr = context.payload.pull_request;
            
            // Extract version from PR title (e.g., "Release Proposal: v1.2.3")
            const versionMatch = pr.title.match(/Release Proposal:\s*(v[\d.]+(?:-[\w.]+)?)/);
            const commitMatch = pr.body.match(/\*\*Target Commit:\*\*\s*`([a-f0-9]+)`/);
            
            if (!versionMatch || !commitMatch) {
              console.log('Could not extract version from title or commit from body');
              return;
            }
            
            const version = versionMatch[1];
            const targetCommit = commitMatch[1];
            
            console.log(`Version: ${version}, Target Commit: ${targetCommit}`);
            
            // Get all reviews
            const reviews = await github.rest.pulls.listReviews({
              owner: context.repo.owner,
              repo: context.repo.repo,
              pull_number: pr.number
            });
            
            // Get list of maintainers
            const maintainerLoginsRaw = process.env.MAINTAINER_LOGINS || '';
            const maintainerLogins = maintainerLoginsRaw
              .split(/[,;]/)
              .map(login => login.trim())
              .filter(login => login.length > 0);
            
            console.log(`Maintainer logins: ${maintainerLogins.join(', ')}`);
            
            // Get the latest review from each user
            const latestReviewsByUser = {};
            reviews.data.forEach(review => {
              const username = review.user.login;
              if (!latestReviewsByUser[username] || new Date(review.submitted_at) > new Date(latestReviewsByUser[username].submitted_at)) {
                latestReviewsByUser[username] = review;
              }
            });
            
            // Count approvals from maintainers
            const maintainerApprovals = Object.entries(latestReviewsByUser)
              .filter(([username, review]) => 
                maintainerLogins.includes(username) && 
                review.state === 'APPROVED'
              )
              .map(([username, review]) => username);
            
            const approvalCount = maintainerApprovals.length;
            console.log(`Found ${approvalCount} maintainer approvals from: ${maintainerApprovals.join(', ')}`);
            
            // Get current labels
            const currentLabels = pr.labels.map(label => label.name);
            const hasApprovedLabel = currentLabels.includes('approved');
            const hasAwaitingApprovalLabel = currentLabels.includes('awaiting-approval');
            
            if (approvalCount >= 2 && !hasApprovedLabel) {
              console.log('✅ Quorum reached! Updating PR...');
              
              // Remove awaiting-approval label if present
              if (hasAwaitingApprovalLabel) {
                await github.rest.issues.removeLabel({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  issue_number: pr.number,
                  name: 'awaiting-approval'
                }).catch(e => console.log('Label not found:', e.message));
              }
              
              // Add approved label
              await github.rest.issues.addLabels({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: pr.number,
                labels: ['approved']
              });
              
              // Add comment with tagging instructions
              const approversList = maintainerApprovals.map(u => `@${u}`).join(', ');
              const commentBody = [
                '## ✅ Approval Quorum Reached',
                '',
                `This release proposal has been approved by ${approvalCount} maintainers: ${approversList}`,
                '',
                '### Tagging Instructions',
                '',
                'A maintainer should now create and push the signed tag:',
                '',
                '```bash',
                `git checkout ${targetCommit}`,
                `git tag -s ${version} -m "Release ${version}"`,
                `git push origin ${version}`,
                `git checkout -`,
                '```',
                '',
                'The release workflow will automatically start when the tag is pushed.'
              ].join('\n');
              
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: pr.number,
                body: commentBody
              });
              
              console.log('Posted tagging instructions');
            } else if (approvalCount < 2 && hasApprovedLabel) {
              console.log('⚠️  Approval count dropped below quorum, removing approved label');
              
              // Remove approved label
              await github.rest.issues.removeLabel({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: pr.number,
                name: 'approved'
              }).catch(e => console.log('Label not found:', e.message));
              
              // Add awaiting-approval label
              if (!hasAwaitingApprovalLabel) {
                await github.rest.issues.addLabels({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  issue_number: pr.number,
                  labels: ['awaiting-approval']
                });
              }
            } else {
              console.log(`⏳ Waiting for more approvals (${approvalCount}/2 required)`);
            }

  handle-pr-closed:
    name: Handle PR Closed Without Tag
    runs-on: ubuntu-latest
    if: |
      contains(github.event.pull_request.labels.*.name, 'release-proposal') &&
      github.event.action == 'closed' && !contains(github.event.pull_request.labels.*.name, 'released')
    
    steps:
      - name: Add cancelled label and comment
        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
        with:
          script: |
            const pr = context.payload.pull_request;
            
            // Check if the release-in-progress label is present
            const hasReleaseInProgress = pr.labels.some(label => label.name === 'release-in-progress');
            
            if (hasReleaseInProgress) {
              // PR was closed while release was in progress - this is unusual
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: pr.number,
                body: '⚠️ **Warning:** This PR was closed while a release was in progress. This may indicate an error. Please verify the release status.'
              });
            } else {
              // PR was closed before tag was created - this is normal cancellation
              const versionMatch = pr.title.match(/Release Proposal:\s*(v[\d.]+(?:-[\w.]+)?)/);
              const version = versionMatch ? versionMatch[1] : 'unknown';
              
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: pr.number,
                body: `## 🚫 Release Proposal Cancelled\n\nThis release proposal for ${version} was closed without creating the tag.\n\nIf you want to proceed with this release later, you can create a new release proposal.`
              });
            }
            
            // Add cancelled label
            await github.rest.issues.addLabels({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: pr.number,
              labels: ['cancelled']
            });
            
            // Remove other workflow labels if present
            const labelsToRemove = ['awaiting-approval', 'approved', 'release-in-progress'];
            for (const label of labelsToRemove) {
              try {
                await github.rest.issues.removeLabel({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  issue_number: pr.number,
                  name: label
                });
              } catch (e) {
                console.log(`Label ${label} not found or already removed`);
              }
            }
            
            console.log('Added cancelled label and cleaned up workflow labels');
            

The same workflow, on Latchkey

Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.

name: Release Proposal Approval Tracker on:  pull_request_review:    types: [submitted, dismissed]  pull_request:    types: [labeled, unlabeled, synchronize, closed] permissions:  contents: read  pull-requests: write  issues: write concurrency:  group: ${{ github.workflow }}-${{ github.ref }}  cancel-in-progress: true jobs:  check-approvals:    timeout-minutes: 30    name: Track Maintainer Approvals    runs-on: latchkey-small    # Only run on PRs with release-proposal label    if: contains(github.event.pull_request.labels.*.name, 'release-proposal') && github.event.pull_request.state == 'open'        steps:      - name: Check approvals and update PR        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0        env:          MAINTAINER_LOGINS: ${{ secrets.MAINTAINER_LOGINS }}        with:          script: |            const pr = context.payload.pull_request;                        // Extract version from PR title (e.g., "Release Proposal: v1.2.3")            const versionMatch = pr.title.match(/Release Proposal:\s*(v[\d.]+(?:-[\w.]+)?)/);            const commitMatch = pr.body.match(/\*\*Target Commit:\*\*\s*`([a-f0-9]+)`/);                        if (!versionMatch || !commitMatch) {              console.log('Could not extract version from title or commit from body');              return;            }                        const version = versionMatch[1];            const targetCommit = commitMatch[1];                        console.log(`Version: ${version}, Target Commit: ${targetCommit}`);                        // Get all reviews            const reviews = await github.rest.pulls.listReviews({              owner: context.repo.owner,              repo: context.repo.repo,              pull_number: pr.number            });                        // Get list of maintainers            const maintainerLoginsRaw = process.env.MAINTAINER_LOGINS || '';            const maintainerLogins = maintainerLoginsRaw              .split(/[,;]/)              .map(login => login.trim())              .filter(login => login.length > 0);                        console.log(`Maintainer logins: ${maintainerLogins.join(', ')}`);                        // Get the latest review from each user            const latestReviewsByUser = {};            reviews.data.forEach(review => {              const username = review.user.login;              if (!latestReviewsByUser[username] || new Date(review.submitted_at) > new Date(latestReviewsByUser[username].submitted_at)) {                latestReviewsByUser[username] = review;              }            });                        // Count approvals from maintainers            const maintainerApprovals = Object.entries(latestReviewsByUser)              .filter(([username, review]) =>                 maintainerLogins.includes(username) &&                 review.state === 'APPROVED'              )              .map(([username, review]) => username);                        const approvalCount = maintainerApprovals.length;            console.log(`Found ${approvalCount} maintainer approvals from: ${maintainerApprovals.join(', ')}`);                        // Get current labels            const currentLabels = pr.labels.map(label => label.name);            const hasApprovedLabel = currentLabels.includes('approved');            const hasAwaitingApprovalLabel = currentLabels.includes('awaiting-approval');                        if (approvalCount >= 2 && !hasApprovedLabel) {              console.log('✅ Quorum reached! Updating PR...');                            // Remove awaiting-approval label if present              if (hasAwaitingApprovalLabel) {                await github.rest.issues.removeLabel({                  owner: context.repo.owner,                  repo: context.repo.repo,                  issue_number: pr.number,                  name: 'awaiting-approval'                }).catch(e => console.log('Label not found:', e.message));              }                            // Add approved label              await github.rest.issues.addLabels({                owner: context.repo.owner,                repo: context.repo.repo,                issue_number: pr.number,                labels: ['approved']              });                            // Add comment with tagging instructions              const approversList = maintainerApprovals.map(u => `@${u}`).join(', ');              const commentBody = [                '## ✅ Approval Quorum Reached',                '',                `This release proposal has been approved by ${approvalCount} maintainers: ${approversList}`,                '',                '### Tagging Instructions',                '',                'A maintainer should now create and push the signed tag:',                '',                '```bash',                `git checkout ${targetCommit}`,                `git tag -s ${version} -m "Release ${version}"`,                `git push origin ${version}`,                `git checkout -`,                '```',                '',                'The release workflow will automatically start when the tag is pushed.'              ].join('\n');                            await github.rest.issues.createComment({                owner: context.repo.owner,                repo: context.repo.repo,                issue_number: pr.number,                body: commentBody              });                            console.log('Posted tagging instructions');            } else if (approvalCount < 2 && hasApprovedLabel) {              console.log('⚠️  Approval count dropped below quorum, removing approved label');                            // Remove approved label              await github.rest.issues.removeLabel({                owner: context.repo.owner,                repo: context.repo.repo,                issue_number: pr.number,                name: 'approved'              }).catch(e => console.log('Label not found:', e.message));                            // Add awaiting-approval label              if (!hasAwaitingApprovalLabel) {                await github.rest.issues.addLabels({                  owner: context.repo.owner,                  repo: context.repo.repo,                  issue_number: pr.number,                  labels: ['awaiting-approval']                });              }            } else {              console.log(`⏳ Waiting for more approvals (${approvalCount}/2 required)`);            }   handle-pr-closed:    timeout-minutes: 30    name: Handle PR Closed Without Tag    runs-on: latchkey-small    if: |      contains(github.event.pull_request.labels.*.name, 'release-proposal') &&      github.event.action == 'closed' && !contains(github.event.pull_request.labels.*.name, 'released')        steps:      - name: Add cancelled label and comment        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0        with:          script: |            const pr = context.payload.pull_request;                        // Check if the release-in-progress label is present            const hasReleaseInProgress = pr.labels.some(label => label.name === 'release-in-progress');                        if (hasReleaseInProgress) {              // PR was closed while release was in progress - this is unusual              await github.rest.issues.createComment({                owner: context.repo.owner,                repo: context.repo.repo,                issue_number: pr.number,                body: '⚠️ **Warning:** This PR was closed while a release was in progress. This may indicate an error. Please verify the release status.'              });            } else {              // PR was closed before tag was created - this is normal cancellation              const versionMatch = pr.title.match(/Release Proposal:\s*(v[\d.]+(?:-[\w.]+)?)/);              const version = versionMatch ? versionMatch[1] : 'unknown';                            await github.rest.issues.createComment({                owner: context.repo.owner,                repo: context.repo.repo,                issue_number: pr.number,                body: `## 🚫 Release Proposal Cancelled\n\nThis release proposal for ${version} was closed without creating the tag.\n\nIf you want to proceed with this release later, you can create a new release proposal.`              });            }                        // Add cancelled label            await github.rest.issues.addLabels({              owner: context.repo.owner,              repo: context.repo.repo,              issue_number: pr.number,              labels: ['cancelled']            });                        // Remove other workflow labels if present            const labelsToRemove = ['awaiting-approval', 'approved', 'release-in-progress'];            for (const label of labelsToRemove) {              try {                await github.rest.issues.removeLabel({                  owner: context.repo.owner,                  repo: context.repo.repo,                  issue_number: pr.number,                  name: label                });              } catch (e) {                console.log(`Label ${label} not found or already removed`);              }            }                        console.log('Added cancelled label and cleaned up workflow labels');             

What changed

  • Run on Latchkey managed runners with one line (runs-on), which apply the fixes below automatically and self-heal transient failures. This example uses latchkey-small; pick the runner size that fits the job.
  • Cancel superseded runs when a branch or PR gets a newer push.
  • Add a job timeout so a hung step cannot burn hours of runner time.

This workflow runs 2 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow

Frequently asked questions

What does the Release Proposal Approval Tracker workflow (caddyserver/caddy) workflow do?
This is the Release Proposal Approval Tracker workflow from the caddyserver/caddy repository, a real project running GitHub Actions. It is shown here with attribution under its Apache-2.0 license.
What CI health grade does this workflow get?
This Automation and other workflow grades C. Paste your own workflow into the Latchkey grader to see its grade and the exact fixes.
How can I improve this Automation and other workflow?
Apply run de-duplication, job timeouts. Latchkey applies these automatically on managed runners when you point runs-on at Latchkey.

References