|
| 1 | +name: Rerun Job |
| 2 | +description: Reruns job of the latest completed run of a workflow for the current PR SHA |
| 3 | + |
| 4 | +inputs: |
| 5 | + workflow-id: |
| 6 | + description: Workflow filename to rerun |
| 7 | + required: false |
| 8 | + default: .github/workflows/pull-request.yml |
| 9 | + token: |
| 10 | + description: 'GitHub token or PAT with actions:write permission' |
| 11 | + required: false |
| 12 | + default: ${{ github.token }} |
| 13 | + job-name: |
| 14 | + description: 'Name of the job to rerun. If not specified, reruns all failed jobs or the entire workflow run based on rerun-failed input' |
| 15 | + required: false |
| 16 | + rerun-failed: |
| 17 | + description: 'If true (default), reruns only failed jobs. If false, triggers a clean run of all jobs' |
| 18 | + required: false |
| 19 | + default: 'true' |
| 20 | + |
| 21 | +runs: |
| 22 | + using: composite |
| 23 | + steps: |
| 24 | + - name: Rerun job(s) |
| 25 | + uses: actions/github-script@v7 |
| 26 | + with: |
| 27 | + github-token: ${{ inputs.token }} |
| 28 | + script: | |
| 29 | + const owner = context.repo.owner; |
| 30 | + const repo = context.repo.repo; |
| 31 | + const headSha = context.payload.pull_request.head.sha; |
| 32 | +
|
| 33 | + const { data: { workflow_runs } } = await github.rest.actions.listWorkflowRuns({ |
| 34 | + owner, repo, |
| 35 | + workflow_id: '${{ inputs.workflow-id }}', |
| 36 | + event: 'pull_request', |
| 37 | + head_sha: headSha, |
| 38 | + per_page: 20, |
| 39 | + }); |
| 40 | +
|
| 41 | + // Skip if a run is already active to avoid concurrency deadlock |
| 42 | + const activeRun = workflow_runs.find(r => |
| 43 | + ['in_progress', 'queued', 'waiting', 'pending', 'requested'].includes(r.status) |
| 44 | + ); |
| 45 | + if (activeRun) { |
| 46 | + core.info(`Workflow run ${activeRun.id} is already ${activeRun.status}, skipping rerun.`); |
| 47 | + return; |
| 48 | + } |
| 49 | +
|
| 50 | + const completedRun = workflow_runs.find(r => r.status === 'completed'); |
| 51 | + if (!completedRun) { |
| 52 | + core.warning('No workflow runs found for the current HEAD SHA.'); |
| 53 | + return; |
| 54 | + } |
| 55 | +
|
| 56 | + const jobName = '${{ inputs.job-name }}'; |
| 57 | + if (jobName) { |
| 58 | + const { data: { jobs } } = await github.rest.actions.listJobsForWorkflowRun({ |
| 59 | + owner, repo, run_id: completedRun.id, filter: 'latest', per_page: 100, |
| 60 | + }); |
| 61 | + const job = jobs.find(j => j.name === jobName); |
| 62 | + if (!job) { |
| 63 | + core.setFailed(`Job "${jobName}" not found in run ${completedRun.id}. Available: ${jobs.map(j => j.name).join(', ')}`); |
| 64 | + return; |
| 65 | + } |
| 66 | + core.info(`Rerunning job "${job.name}" (id: ${job.id}) from run ${completedRun.id}`); |
| 67 | + await github.rest.actions.reRunJobForWorkflowRun({ owner, repo, job_id: job.id }); |
| 68 | + return; |
| 69 | + } |
| 70 | +
|
| 71 | + const rerunFailed = '${{ inputs.rerun-failed }}' === 'true'; |
| 72 | + if (rerunFailed) { |
| 73 | + core.info(`Rerunning failed jobs for run ${completedRun.id} (conclusion: ${completedRun.conclusion})`); |
| 74 | + await github.rest.actions.reRunWorkflowFailedJobs({ owner, repo, run_id: completedRun.id }); |
| 75 | + } else { |
| 76 | + core.info(`Rerunning all jobs for run ${completedRun.id} (conclusion: ${completedRun.conclusion})`); |
| 77 | + await github.rest.actions.reRunWorkflow({ owner, repo, run_id: completedRun.id }); |
| 78 | + } |
0 commit comments