From fd01bd855c439401bbdbb916951635f236a5119f Mon Sep 17 00:00:00 2001 From: Matiss Janis Aboltins Date: Thu, 14 May 2026 20:41:13 +0100 Subject: [PATCH] [AI] Stabilize size-compare job by pinning downloads to run_id (#7780) * [AI] Stabilize size-compare job by pinning downloads to run_id The compare job in .github/workflows/size-compare.yml was flaky because fountainhead/action-wait-for-check matched a check by name from any run on the branch, while dawidd6/action-download-artifact with branch:/pr: filters and workflow_conclusion: '' resolved to the latest run regardless of completion. When a new master build started in the seconds between waiting and downloading, the action picked up the in-progress run and failed with "artifact not found". Replaces the eight wait-for-check steps with one actions/github-script step that polls listWorkflowRuns for a successful build.yml run on master and the PR head SHA in parallel via Promise.all, then pins all eight downloads to those run_ids. Co-Authored-By: Claude Opus 4.7 (1M context) * Add release notes for PR #7780 * Change category to Maintenance in release notes Updated category from 'Enhancements' to 'Maintenance'. * [AI] Clean up comment to remove reference to previous implementation Co-authored-by: Matiss Janis Aboltins --------- Co-authored-by: Claude Opus 4.7 (1M context) Co-authored-by: github-actions[bot] Co-authored-by: Cursor Agent Co-authored-by: Matiss Janis Aboltins --- .github/workflows/size-compare.yml | 145 +++++++++++++---------------- upcoming-release-notes/7780.md | 6 ++ 2 files changed, 69 insertions(+), 82 deletions(-) create mode 100644 upcoming-release-notes/7780.md diff --git a/.github/workflows/size-compare.yml b/.github/workflows/size-compare.yml index 55e2db41ef..08e056e55b 100644 --- a/.github/workflows/size-compare.yml +++ b/.github/workflows/size-compare.yml @@ -33,6 +33,7 @@ jobs: permissions: pull-requests: write contents: read + actions: read steps: - name: Checkout base branch uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -44,140 +45,120 @@ jobs: with: download-translations: 'false' - - name: Wait for ${{github.base_ref}} web build to succeed - uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0 - id: master-web-build + # Resolve one successful `build.yml` run for each side (master and PR + # head) up front, then pin every download below to its `run_id`. This + # ensures artifact downloads are consistent and prevents race conditions. + - name: Resolve build runs + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + id: build-runs + env: + BASE_REF: ${{ github.base_ref }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} with: - token: ${{ secrets.GITHUB_TOKEN }} - checkName: web - ref: ${{github.base_ref}} - - name: Wait for ${{github.base_ref}} API build to succeed - uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0 - id: master-api-build - with: - token: ${{ secrets.GITHUB_TOKEN }} - checkName: api - ref: ${{github.base_ref}} - - name: Wait for ${{github.base_ref}} CLI build to succeed - uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0 - id: master-cli-build - with: - token: ${{ secrets.GITHUB_TOKEN }} - checkName: cli - ref: ${{github.base_ref}} - - name: Wait for ${{github.base_ref}} CRDT build to succeed - uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0 - id: master-crdt-build - with: - token: ${{ secrets.GITHUB_TOKEN }} - checkName: crdt - ref: ${{github.base_ref}} + script: | + const TIMEOUT_MS = 30 * 60 * 1000; + const SLEEP_MS = 15000; - - name: Wait for PR build to succeed - uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0 - id: wait-for-web-build - with: - token: ${{ secrets.GITHUB_TOKEN }} - checkName: web - ref: ${{github.event.pull_request.head.sha}} - - name: Wait for API PR build to succeed - uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0 - id: wait-for-api-build - with: - token: ${{ secrets.GITHUB_TOKEN }} - checkName: api - ref: ${{github.event.pull_request.head.sha}} - - name: Wait for CLI PR build to succeed - uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0 - id: wait-for-cli-build - with: - token: ${{ secrets.GITHUB_TOKEN }} - checkName: cli - ref: ${{github.event.pull_request.head.sha}} - - name: Wait for CRDT PR build to succeed - uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0 - id: wait-for-crdt-build - with: - token: ${{ secrets.GITHUB_TOKEN }} - checkName: crdt - ref: ${{github.event.pull_request.head.sha}} + async function resolveRun({ label, filter, notFoundHint }) { + const deadline = Date.now() + TIMEOUT_MS; + while (true) { + const { data } = await github.rest.actions.listWorkflowRuns({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'build.yml', + ...filter, + status: 'success', + per_page: 1, + }); + if (data.workflow_runs.length > 0) { + const run = data.workflow_runs[0]; + core.info(`Found ${label} build run ${run.id} (${run.html_url})`); + return run.id; + } + if (Date.now() > deadline) { + throw new Error( + `No successful build.yml run found for ${label} within 30 min — ${notFoundHint}.`, + ); + } + core.info(`No successful ${label} build run yet — sleeping 15s.`); + await new Promise(r => setTimeout(r, SLEEP_MS)); + } + } - - name: Report build failure - if: steps.wait-for-web-build.outputs.conclusion == 'failure' || steps.wait-for-api-build.outputs.conclusion == 'failure' || steps.wait-for-cli-build.outputs.conclusion == 'failure' || steps.wait-for-crdt-build.outputs.conclusion == 'failure' - run: | - echo "Build failed on PR branch or ${GITHUB_BASE_REF}" - exit 1 + const baseRef = process.env.BASE_REF; + const headSha = process.env.HEAD_SHA; + const [masterRunId, headRunId] = await Promise.all([ + resolveRun({ + label: baseRef, + filter: { branch: baseRef }, + notFoundHint: `${baseRef} may be broken`, + }), + resolveRun({ + label: `PR head ${headSha}`, + filter: { head_sha: headSha }, + notFoundHint: + 'build may still be running, have failed, or the branch may have been force-pushed', + }), + ]); + core.setOutput('master_run_id', masterRunId); + core.setOutput('head_run_id', headRunId); - name: Download web build artifact from ${{github.base_ref}} uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20 - id: pr-web-build with: - branch: ${{github.base_ref}} + run_id: ${{ steps.build-runs.outputs.master_run_id }} workflow: build.yml - workflow_conclusion: '' # ignore the conclusion of the workflow, since we already checked it name: build-stats path: base - name: Download API build artifact from ${{github.base_ref}} uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20 - id: pr-api-build with: - branch: ${{github.base_ref}} + run_id: ${{ steps.build-runs.outputs.master_run_id }} workflow: build.yml - workflow_conclusion: '' # ignore the conclusion of the workflow, since we already checked it name: api-build-stats path: base - name: Download build stats from PR uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20 with: - pr: ${{github.event.pull_request.number}} + run_id: ${{ steps.build-runs.outputs.head_run_id }} workflow: build.yml - workflow_conclusion: '' # ignore the conclusion of the workflow, since we already checked it name: build-stats path: head - allow_forks: true - name: Download API stats from PR uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20 with: - pr: ${{github.event.pull_request.number}} + run_id: ${{ steps.build-runs.outputs.head_run_id }} workflow: build.yml - workflow_conclusion: '' # ignore the conclusion of the workflow, since we already checked it name: api-build-stats path: head - allow_forks: true - name: Download CLI build artifact from ${{github.base_ref}} uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20 with: - branch: ${{github.base_ref}} + run_id: ${{ steps.build-runs.outputs.master_run_id }} workflow: build.yml - workflow_conclusion: '' # ignore the conclusion of the workflow, since we already checked it name: cli-build-stats path: base - name: Download CLI stats from PR uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20 with: - pr: ${{github.event.pull_request.number}} + run_id: ${{ steps.build-runs.outputs.head_run_id }} workflow: build.yml - workflow_conclusion: '' # ignore the conclusion of the workflow, since we already checked it name: cli-build-stats path: head - allow_forks: true - name: Download CRDT build artifact from ${{github.base_ref}} uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20 with: - branch: ${{github.base_ref}} + run_id: ${{ steps.build-runs.outputs.master_run_id }} workflow: build.yml - workflow_conclusion: '' # ignore the conclusion of the workflow, since we already checked it name: crdt-build-stats path: base - name: Download CRDT stats from PR uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20 with: - pr: ${{github.event.pull_request.number}} + run_id: ${{ steps.build-runs.outputs.head_run_id }} workflow: build.yml - workflow_conclusion: '' # ignore the conclusion of the workflow, since we already checked it name: crdt-build-stats path: head - allow_forks: true - name: Strip content hashes from stats files run: | if [ -f ./head/web-stats.json ]; then diff --git a/upcoming-release-notes/7780.md b/upcoming-release-notes/7780.md new file mode 100644 index 0000000000..12da632383 --- /dev/null +++ b/upcoming-release-notes/7780.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MatissJanis] +--- + +Stabilize size comparison workflow by pinning artifact downloads to specific run IDs.