From 3ab6d9096ebdfe3e09593da1154f4a1d6120e912 Mon Sep 17 00:00:00 2001 From: Keith Williams Date: Mon, 5 Jan 2026 11:58:20 -0300 Subject: [PATCH] fix: cancel running CI workflow before re-triggering and allow trusted GitHub Apps (#26461) * fix: cancel running CI workflow before re-triggering and allow trusted bots Co-Authored-By: keith@cal.com * fix: remove hardcoded bot allowlist, keep only cancel-and-rerun improvement Co-Authored-By: keith@cal.com * fix: add app_id verification for trusted GitHub Apps (Graphite) Co-Authored-By: keith@cal.com * fix: simplify trusted bot check to use sender type, login, and installation context Co-Authored-By: keith@cal.com * chore: remove unnecessary comments Co-Authored-By: keith@cal.com --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .github/workflows/run-ci.yml | 78 +++++++++++++++++++++++++++++------- 1 file changed, 64 insertions(+), 14 deletions(-) diff --git a/.github/workflows/run-ci.yml b/.github/workflows/run-ci.yml index 452de59ab5..1d96a65ce2 100644 --- a/.github/workflows/run-ci.yml +++ b/.github/workflows/run-ci.yml @@ -19,21 +19,32 @@ jobs: with: script: | const adder = context.payload.sender.login; + const senderType = context.payload.sender.type; const pr = context.payload.pull_request; + const installation = context.payload.installation; - // Verify label adder has write access - const { data: perm } = await github.rest.repos.getCollaboratorPermissionLevel({ - owner: context.repo.owner, - repo: context.repo.repo, - username: adder, - }); + const trustedBotLogins = ['graphite-app[bot]']; + let isAuthorized = false; - if (!['admin', 'maintain', 'write'].includes(perm.permission)) { - core.setFailed(`${adder} does not have write access`); - return; + if (senderType === 'Bot' && installation && trustedBotLogins.includes(adder)) { + console.log(`Label added by trusted GitHub App: ${adder}`); + isAuthorized = true; } - console.log(`Label added by ${adder} (${perm.permission})`); + if (!isAuthorized) { + const { data: perm } = await github.rest.repos.getCollaboratorPermissionLevel({ + owner: context.repo.owner, + repo: context.repo.repo, + username: adder, + }); + + if (!['admin', 'maintain', 'write'].includes(perm.permission)) { + core.setFailed(`${adder} does not have write access`); + return; + } + + console.log(`Label added by ${adder} (${perm.permission})`); + } // Find the latest pr.yml run for this PR's head SHA const { data: runs } = await github.rest.actions.listWorkflowRuns({ @@ -56,13 +67,52 @@ jobs: const latestRun = matchingRuns[0]; - // Check if workflow is still running - can't re-run in-progress workflows + // If workflow is still running, cancel it first then re-run if (latestRun.status === 'in_progress' || latestRun.status === 'queued') { - core.setFailed(`Workflow is still running (status: ${latestRun.status}). Wait for it to complete or cancel it first.`); - return; + console.log(`Workflow is running (status: ${latestRun.status}). Cancelling it first...`); + + await github.rest.actions.cancelWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: latestRun.id, + }); + + // Wait for the workflow to be cancelled (poll with timeout) + const maxWaitTime = 60000; // 60 seconds + const pollInterval = 2000; // 2 seconds + const startTime = Date.now(); + + while (Date.now() - startTime < maxWaitTime) { + await new Promise(resolve => setTimeout(resolve, pollInterval)); + + const { data: updatedRun } = await github.rest.actions.getWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: latestRun.id, + }); + + if (updatedRun.status === 'completed') { + console.log(`Workflow cancelled successfully (conclusion: ${updatedRun.conclusion})`); + break; + } + + console.log(`Waiting for workflow to cancel... (status: ${updatedRun.status})`); + } + + // Check final status + const { data: finalRun } = await github.rest.actions.getWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: latestRun.id, + }); + + if (finalRun.status !== 'completed') { + core.setFailed(`Timed out waiting for workflow to cancel (status: ${finalRun.status})`); + return; + } } - console.log(`Re-running workflow ${latestRun.id} (was: ${latestRun.conclusion})`); + console.log(`Re-running workflow ${latestRun.id} (was: ${latestRun.conclusion || latestRun.status})`); // Re-run preserves original context (PR, SHA, etc.) await github.rest.actions.reRunWorkflow({