Repository navigation
fix(ci): wait for the refresh commit's own checks before merging - #624
Conversation
The hourly aggregate refresh force-pushes chore/aggregate-refresh and then ran `gh pr checks --watch --fail-fast` immediately. At that instant GitHub has not registered any check on the new head, so gh exits "no checks reported" and the job fails without merging. Every scheduled run has failed this way since 2026-10-24 18:28 UTC, and refresh PR #617 sits green but unmerged. Add scripts/await-head-checks.ts, which polls the check runs of the pushed SHA (so the previous head's checks never count), waits a bounded time for checks to register, then for all of them to finish, fails fast on any failure, and only succeeds once "CI / required" (main's required check) passed. The workflow calls it with the SHA it pushed, keys the built-in-token dispatch fallback's run lookup on the same SHA, and merges with --match-head-commit so it can only merge the commit it watched. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
This PR has been added to the review queue. Track progress: https://factory.gascity.com/city/maintainer-city/runs/gcg--9223372036836987040 |
…token Two refresh runs can overlap: GitHub's cron fires late and unevenly (runs 39 minutes apart are in the history) and the new wait can last up to an hour. Each run force-pushes the same branch, so the newer push replaces the commit the older run is waiting on. The older run then fails for nothing, either because image.yml cancels that commit's build or because its --match-head-commit merge is refused. Add a workflow concurrency group that queues the newer run instead of cancelling the running one. The job runs on an App installation token that expires after one hour, but the helper could wait up to 3600s after the push, so a slow CI would end in 401s instead of a clean timeout. Cap the wait at 2700s and the job at 60 minutes. The job cap also bounds how long a stuck run can hold the group. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Max-effort review: approve and mergeVerdict: clean after two Low fixes, which I pushed as
Findings
Verified correct
Verification (Bun 1.4.4, as in CI)
End-to-end: the first scheduled run after the merge should reuse #617, wait for its new head's checks and merge it. I'll report that run here. 🤖 Generated with Claude Code |
End-to-end result: the hourly refresh works againRun 40274509516 (schedule, 05:29Z) was the first scheduled run after the merge (
Run 40282364257 (schedule, 06:52Z, the next one) logged No code-scanning alerts were introduced. The only open one is the pre-existing Scorecard CodeReview process item (#7). 🤖 Generated with Claude Code |
Summary
The hourly Update Aggregate Registry workflow has failed on every run since 2026-10-24 18:28 UTC, and the refresh PR #617 is green but never merges.
Root cause. The refresh step force-pushes
chore/aggregate-refreshand then, on the App-token path, immediately runsgh pr checks "$pr" --watch --fail-fast. At that moment GitHub has not yet registered any check on the new head, soghprintsno checks reported on the 'chore/aggregate-refresh' branch, exits non-zero, and the job fails about a second after the push. CI then runs and passes on the PR, but nothing is left to merge it. Example from run 40239867630:The first failure, run 40120817169 (2026-10-24 18:27 UTC), opened #617 and failed the same way. Later examples: 40235202615, 40230605523, 40225557675. There was also a second, latent problem:
gh pr checksreads the PR's head when it runs, so right after a force-push it could see the previous head's green checks, and the unpinnedgh pr mergecould then merge a commit nobody had watched.Fix
scripts/await-head-checks.ts, a small Bun helper with a pureevaluateChecksand an injectable polling loop. It pollsGET /repos/{repo}/commits/{sha}/check-runsfor the SHA the job just pushed, so the previous head's checks can never count. It then:--register-timeout(600s) for any check to register on that commit;--timeout(3600s) for every registered check to finish, and fails fast on any failed, cancelled, timed-out or action-required conclusion;--requirecheck (CI / required, the check main's branch protection requires) has succeeded. This also covers the gap whereverifyis green but the dependentCI / requiredjob hasn't registered yet, a gap where "all reported checks green" would be a false positive.update-registry.yml: capturessha=$(git rev-parse HEAD)after the push and calls the helper instead ofgh pr checks --watch. The built-in-token fallback's dispatched-run lookup now also filters on--commit "$sha", since previously it could pick up the previous push's run on the same branch. The merge isgh pr merge --squash --match-head-commit "$sha", so the job can only merge the commit it watched.Testing
bun test scripts/await-head-checks.test.ts: 21 pass. The tests cover:Two workflow assertions fail against the current
mainworkflow and pass with this change.bun run test:unit: 504 pass, 0 fail. This includesscripts/supply-chain-pins.test.ts: every action is still SHA-pinned and there are no top-level write scopes.bun run typecheck,bun run generate:checkandbun auditall pass.actionlint(with shellcheck): no findings forupdate-registry.ymlapart from the existing custom runner-label notice forblacksmith-2vcpu-ubuntu-2404.Live read-only run of the helper against the real API:
a41ccac: exits 0 onceCI / required, CodeQL, Analyze (actions), Analyze (javascript-typescript), verify, imagehave all passed.I did not run the workflow via
workflow_dispatchon this branch. The job checks outref: main, so on this branch it would run the new YAML against main's tree, where the helper doesn't exist yet. It would also force-push the live refresh branch and try to merge tomainfrom unreviewed code. The first scheduled run after this merges (at :17 past the hour) is the end-to-end check. It should reuse chore: update aggregate registry #617, wait for the new head's checks and merge it.Checklist
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.