From d245b9b640634fde30118edf2d27772289195a4e Mon Sep 17 00:00:00 2001 From: Mark Allen Date: Sat, 2 Aug 2025 11:37:08 +0200 Subject: [PATCH 1/2] fix: prevent auto-merge workflow from waiting for itself - Filter out auto-merge workflow checks to avoid circular dependency - Only wait for meaningful CI checks (CI, test, build, lint, RuboCop) - Add debug logging to help troubleshoot check detection - Add fallback for cases with no relevant CI checks to wait for This resolves the issue where the auto-merge workflow would hang indefinitely waiting for itself to complete. --- .github/workflows/auto-merge.yml | 34 ++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml index df4cad1..625f9e6 100644 --- a/.github/workflows/auto-merge.yml +++ b/.github/workflows/auto-merge.yml @@ -67,13 +67,36 @@ jobs: }); const allChecks = [...checks.check_runs, ...statuses]; - const pendingChecks = allChecks.filter(check => + + // Filter out this workflow to avoid waiting for itself + const relevantChecks = allChecks.filter(check => { + const checkName = check.name || check.context || ''; + // Exclude auto-merge workflows + if (checkName.includes('auto-merge') || checkName.includes('Auto-merge')) { + return false; + } + // Only wait for actual CI/test checks + return checkName.includes('CI') || + checkName.includes('test') || + checkName.includes('build') || + checkName.includes('lint') || + checkName.includes('RuboCop') || + checkName === 'ci'; // Our specific CI workflow + }); + + console.log(`Total checks found: ${allChecks.length}`); + console.log(`Relevant checks after filtering: ${relevantChecks.length}`); + if (relevantChecks.length > 0) { + console.log('Relevant checks:', relevantChecks.map(c => c.name || c.context).join(', ')); + } + + const pendingChecks = relevantChecks.filter(check => check.status === 'queued' || check.status === 'in_progress' || check.state === 'pending' ); - const failedChecks = allChecks.filter(check => + const failedChecks = relevantChecks.filter(check => check.conclusion === 'failure' || check.conclusion === 'cancelled' || check.state === 'failure' || @@ -86,9 +109,12 @@ jobs: return; } - if (pendingChecks.length === 0) { + if (relevantChecks.length === 0) { + console.log('No relevant CI checks found, proceeding with merge'); + allChecksPassed = true; + } else if (pendingChecks.length === 0) { allChecksPassed = true; - console.log('All checks passed!'); + console.log('All relevant checks passed!'); } else { console.log(`Waiting for ${pendingChecks.length} checks to complete...`); console.log('Pending checks:', pendingChecks.map(c => c.name || c.context).join(', ')); From bc4da504165f6154600928d10617ae197e3449e9 Mon Sep 17 00:00:00 2001 From: Mark Allen Date: Sat, 2 Aug 2025 11:38:37 +0200 Subject: [PATCH 2/2] feat: replace complex auto-merge workflow with reliable Dependabot solution - Remove problematic auto-merge.yml that waited for itself (circular dependency) - Add dependabot-auto-merge.yml using official Dependabot metadata action - Use GitHub's built-in auto-merge feature instead of custom implementation - Only auto-merge patch version updates for safety - Simpler, more reliable approach that doesn't hang The new workflow uses the proven dependabot/fetch-metadata action and GitHub's native auto-merge functionality, eliminating the circular dependency issue with the previous custom implementation. --- .github/workflows/auto-merge.yml | 155 -------------------- .github/workflows/dependabot-auto-merge.yml | 23 +++ 2 files changed, 23 insertions(+), 155 deletions(-) delete mode 100644 .github/workflows/auto-merge.yml create mode 100644 .github/workflows/dependabot-auto-merge.yml diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml deleted file mode 100644 index 625f9e6..0000000 --- a/.github/workflows/auto-merge.yml +++ /dev/null @@ -1,155 +0,0 @@ -name: Auto-merge Dependabot PRs - -on: - pull_request: - types: [opened, synchronize, reopened] - -permissions: - contents: write - pull-requests: write - checks: read - -jobs: - auto-merge: - runs-on: ubuntu-latest - if: github.actor == 'dependabot[bot]' - - steps: - - name: Check if PR is patch update - id: check-patch - uses: actions/github-script@v7 - with: - script: | - const title = context.payload.pull_request.title; - const isPatch = title.includes('patch') || - title.match(/bump .+ from \d+\.\d+\.\d+ to \d+\.\d+\.\d+$/); - const isSecurityUpdate = title.includes('security') || - context.payload.pull_request.labels.some(label => - label.name === 'security' || label.name === 'vulnerability' - ); - - console.log(`PR Title: ${title}`); - console.log(`Is patch update: ${isPatch}`); - console.log(`Is security update: ${isSecurityUpdate}`); - - return { - should_auto_merge: isPatch || isSecurityUpdate, - is_patch: isPatch, - is_security: isSecurityUpdate - }; - - - name: Wait for CI to complete - if: fromJSON(steps.check-patch.outputs.result).should_auto_merge - uses: actions/github-script@v7 - with: - script: | - const { owner, repo } = context.repo; - const pr_number = context.payload.pull_request.number; - - // Wait for all checks to complete - let allChecksPassed = false; - let attempts = 0; - const maxAttempts = 30; // Wait up to 15 minutes (30 * 30s) - - while (!allChecksPassed && attempts < maxAttempts) { - attempts++; - - const { data: checks } = await github.rest.checks.listForRef({ - owner, - repo, - ref: context.payload.pull_request.head.sha, - }); - - const { data: statuses } = await github.rest.repos.listCommitStatusesForRef({ - owner, - repo, - ref: context.payload.pull_request.head.sha, - }); - - const allChecks = [...checks.check_runs, ...statuses]; - - // Filter out this workflow to avoid waiting for itself - const relevantChecks = allChecks.filter(check => { - const checkName = check.name || check.context || ''; - // Exclude auto-merge workflows - if (checkName.includes('auto-merge') || checkName.includes('Auto-merge')) { - return false; - } - // Only wait for actual CI/test checks - return checkName.includes('CI') || - checkName.includes('test') || - checkName.includes('build') || - checkName.includes('lint') || - checkName.includes('RuboCop') || - checkName === 'ci'; // Our specific CI workflow - }); - - console.log(`Total checks found: ${allChecks.length}`); - console.log(`Relevant checks after filtering: ${relevantChecks.length}`); - if (relevantChecks.length > 0) { - console.log('Relevant checks:', relevantChecks.map(c => c.name || c.context).join(', ')); - } - - const pendingChecks = relevantChecks.filter(check => - check.status === 'queued' || - check.status === 'in_progress' || - check.state === 'pending' - ); - - const failedChecks = relevantChecks.filter(check => - check.conclusion === 'failure' || - check.conclusion === 'cancelled' || - check.state === 'failure' || - check.state === 'error' - ); - - if (failedChecks.length > 0) { - console.log('Some checks failed, will not auto-merge'); - console.log('Failed checks:', failedChecks.map(c => c.name || c.context).join(', ')); - return; - } - - if (relevantChecks.length === 0) { - console.log('No relevant CI checks found, proceeding with merge'); - allChecksPassed = true; - } else if (pendingChecks.length === 0) { - allChecksPassed = true; - console.log('All relevant checks passed!'); - } else { - console.log(`Waiting for ${pendingChecks.length} checks to complete...`); - console.log('Pending checks:', pendingChecks.map(c => c.name || c.context).join(', ')); - await new Promise(resolve => setTimeout(resolve, 30000)); // Wait 30 seconds - } - } - - if (!allChecksPassed) { - console.log('Timeout waiting for checks to complete'); - return; - } - - // Enable auto-merge - await github.rest.pulls.merge({ - owner, - repo, - pull_number: pr_number, - merge_method: 'squash', - commit_title: `${context.payload.pull_request.title} (#${pr_number})`, - commit_message: 'Auto-merged by Dependabot workflow' - }); - - console.log('PR auto-merged successfully!'); - - - name: Add comment on successful merge - if: fromJSON(steps.check-patch.outputs.result).should_auto_merge - uses: actions/github-script@v7 - with: - script: | - const result = ${{ steps.check-patch.outputs.result }}; - const updateType = result.is_security ? 'security update' : 'patch update'; - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.payload.pull_request.number, - body: `🤖 Auto-merged this ${updateType} after all checks passed successfully!` - }); diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 0000000..efbfa64 --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,23 @@ +name: Dependabot auto-merge +on: pull_request + +permissions: + contents: write + pull-requests: write + +jobs: + dependabot: + runs-on: ubuntu-latest + if: ${{ github.actor == 'dependabot[bot]' }} + steps: + - name: Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@v2 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + - name: Enable auto-merge for Dependabot PRs + if: ${{steps.metadata.outputs.update-type == 'version-update:semver-patch'}} + run: gh pr merge --auto --merge "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}