Skip to content

Commit d513348

Browse files
committed
Implement Early Exit Pattern in release workflow
Problem: - release.yml was triggered 4 separate times (once per workflow completion) - Each run was independent, causing potential duplicate releases Solution: - Added check-all-workflows job that verifies all 4 required workflows (wheels, wheels-docker, wstest, main) have completed successfully - Uses GitHub API to query workflow runs for the current commit SHA - Exits early (skips all jobs) if any workflow is still pending - Only proceeds with full release when ALL workflows complete Behavior: - Run 1 (first workflow completes): Check fails, exit early - Run 2 (second workflow completes): Check fails, exit early - Run 3 (third workflow completes): Check fails, exit early - Run 4 (fourth workflow completes): Check passes, full release proceeds Benefits: - Single consolidated release (no duplicates) - Efficient artifact collection from all 4 workflows - Clear logging shows which workflows are pending - Idempotent and race-condition safe
1 parent 98ba994 commit d513348

1 file changed

Lines changed: 71 additions & 5 deletions

File tree

.github/workflows/release.yml

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,71 @@ on:
1010
workflow_dispatch:
1111

1212
jobs:
13+
check-all-workflows:
14+
name: Check if all workflows completed
15+
runs-on: ubuntu-latest
16+
outputs:
17+
all_complete: ${{ steps.check.outputs.all_complete }}
18+
19+
steps:
20+
- name: Check all required workflows completed
21+
id: check
22+
uses: actions/github-script@v7
23+
with:
24+
script: |
25+
const requiredWorkflows = ['wheels', 'wheels-docker', 'wstest', 'main'];
26+
const commitSha = context.payload.workflow_run.head_sha;
27+
28+
console.log('─────────────────────────────────────────────────');
29+
console.log('🔍 Checking workflow completion status');
30+
console.log('─────────────────────────────────────────────────');
31+
console.log(`Commit SHA: ${commitSha}`);
32+
console.log(`Triggered by: ${context.payload.workflow_run.name}`);
33+
console.log('');
34+
35+
// Get all workflow runs for this commit
36+
const { data: runs } = await github.rest.actions.listWorkflowRunsForRepo({
37+
owner: context.repo.owner,
38+
repo: context.repo.repo,
39+
head_sha: commitSha,
40+
per_page: 100
41+
});
42+
43+
// Group by workflow name and find latest run for each
44+
const latestRuns = {};
45+
for (const run of runs.workflow_runs) {
46+
const workflowName = run.name;
47+
if (requiredWorkflows.includes(workflowName)) {
48+
if (!latestRuns[workflowName] || run.id > latestRuns[workflowName].id) {
49+
latestRuns[workflowName] = run;
50+
}
51+
}
52+
}
53+
54+
// Check if all required workflows completed successfully
55+
console.log('Required workflows status:');
56+
const allComplete = requiredWorkflows.every(name => {
57+
const run = latestRuns[name];
58+
const complete = run && run.status === 'completed' && run.conclusion === 'success';
59+
const status = run ? `${run.status}/${run.conclusion}` : 'not found';
60+
console.log(` ${complete ? '✅' : '⏳'} ${name.padEnd(20)} : ${status}`);
61+
return complete;
62+
});
63+
64+
console.log('');
65+
if (!allComplete) {
66+
console.log('⏳ Not all workflows complete yet - exiting early');
67+
console.log(' This is normal! Release will proceed once all workflows finish.');
68+
} else {
69+
console.log('✅ All workflows complete - proceeding with release!');
70+
}
71+
console.log('─────────────────────────────────────────────────');
72+
73+
core.setOutput('all_complete', allComplete ? 'true' : 'false');
74+
1375
identifiers:
76+
needs: check-all-workflows
77+
if: needs.check-all-workflows.outputs.all_complete == 'true'
1478
# GitHub needs to know where .cicd/workflows/identifiers.yml lives at parse time,
1579
# and submodules aren't included in that context! thus the following does NOT work:
1680
# uses: ./.cicd/workflows/identifiers.yml
@@ -22,15 +86,15 @@ jobs:
2286
# Nightly and stable GitHub releases (consolidates wheels from both workflows)
2387
release-nightly:
2488
name: Nightly & Stable GitHub Releases
25-
needs: identifiers
89+
needs: [check-all-workflows, identifiers]
2690
runs-on: ubuntu-latest
2791

2892
# Only create releases for nightly and stable builds (explicit positive list)
2993
if: |
94+
needs.check-all-workflows.outputs.all_complete == 'true' &&
3095
github.event_name == 'workflow_run' &&
3196
github.event.workflow_run.conclusion == 'success' &&
32-
(needs.identifiers.outputs.release_type == 'nightly' || needs.identifiers.outputs.release_type == 'stable') &&
33-
(github.event.workflow_run.name == 'wheels' || github.event.workflow_run.name == 'wheels-docker')
97+
(needs.identifiers.outputs.release_type == 'nightly' || needs.identifiers.outputs.release_type == 'stable')
3498
3599
env:
36100
RELEASE_TYPE: ${{ needs.identifiers.outputs.release_type }}
@@ -193,11 +257,13 @@ jobs:
193257
# Stable release publishing: PyPI and RTD (consolidates from both wheel workflows)
194258
release-stable:
195259
name: Stable Release (PyPI & RTD)
196-
needs: [identifiers, release-nightly]
260+
needs: [check-all-workflows, identifiers, release-nightly]
197261
runs-on: ubuntu-latest
198262

199263
# Only publish to PyPI for stable releases (explicit positive list)
200-
if: needs.identifiers.outputs.release_type == 'stable'
264+
if: |
265+
needs.check-all-workflows.outputs.all_complete == 'true' &&
266+
needs.identifiers.outputs.release_type == 'stable'
201267
202268
env:
203269
RELEASE_TYPE: ${{ needs.identifiers.outputs.release_type }}

0 commit comments

Comments
 (0)