Summary
Under queue saturation, the scheduler's pre-review update_branch path discards in-flight checks. A scheduler run waits 2h+ for a runner, executes after main has moved, picks one behind PR from its scan — its own PR (#1926) or another one (a codex/originweave-mv3-owner run updated #1857) — and merges main into that head before any review is dispatched. Every completing run therefore has one near-random victim, and the scheduler's completion rate is the PR cancellation rate. The push cancels every queued check on the old head and puts the new head at the back of the queue. main moves roughly every 28 minutes (77 merges into this repository in ~36h), so no PR head survives long enough for its 12 required contexts to complete.
Measured on #1926 (2026-09-05):
| item |
value |
| scheduler run for the PR |
33963177129, pull_request_target, created 11:22:53Z, completed 13:44:41Z (success) |
bot push c730ae0b (Merge branch 'main' into …) |
13:44:37Z |
| scheduler log line |
PR #1926: update_branch: current head has no OpenCode approval; branch is outdated before review dispatch; branch update requested with OpenCode app token inside GitHub Actions as OpenCode GitHub App |
check-runs on the replaced head 7920a70b |
28 total: 22 cancelled within seconds of the push (queued since 11:22:53Z), 3 success, 3 skipped |
Same mechanism observed on #1812 (c9244ceb, 13:31:29Z) and, after the #1926 merge, on #1484 (777ee3ed, 13:58:42Z: 21 of 30 check-runs on the old head cancelled at the push, 8 successes orphaned, new head starts from zero). The pushes are not a burst caused by the merge: they are a steady drip, one per PR scheduler run that finishes its queue wait, and with main moving every ~28 minutes almost every run finds its PR behind. The 5–12 bot-pushed branches seen in the 20 minutes after 6f8c51d7 are the scheduler runs that happened to complete in that window.
Consequence on merge governance
Reconstructing the check state at merged_at for every PR merged into .github since 2026-09-04 (77 PRs, REST commits/{sha}/check-runs, only runs completed before merged_at counted, skipped counted as satisfied):
| required contexts satisfied at merge |
PRs |
| 0 / 12 |
76 |
| 7 / 12 |
1 |
| 12 / 12 |
0 |
Depth ≥ 2 jobs (opencode-review, coverage-evidence, trivy-fs, scorecard, osv-scan, dependency-review, both CodeQL compatibility analysis (...)) had no check-run at all before merge on 75–77 heads: their upstream gate job never got a runner. Every merge since 09-04 was therefore an admin bypass with the required review evidence absent. merged_by is populated on these, so it is not a bypass signal; the check state at merged_at is.
Where
.github/workflows/pr-review-merge-scheduler.yml:139 — UPDATE_BRANCHES is true for pull_request_target runs, so every PR's own event run performs the update.
scripts/ci/pr_review_merge_scheduler_core.py:4774-4785 — if behind_by and trigger_reviews: → update_branch; there is no check for required check-runs still queued/in progress on the current head.
CLAUDE.md states the scheduler "updates a PR branch only when the latest review is approved, no current-head check has failed, and GitHub reports the PR as behind". The pre-review path above is a second, undocumented update path.
Proposed fix (smallest root-cause change)
- In
pr_review_merge_scheduler_core.py, before the pre-review update_branch, decide wait when the current head still has required check-runs in queued/in_progress — do not discard evidence in flight. One helper, one guard, one contract test.
- Optionally, set
UPDATE_BRANCHES to false for pull_request_target runs and leave the refresh to the schedule/push sweeps, which are rate-limited by BRANCH_UPDATE_LIMIT.
- Correct the
CLAUDE.md sentence to describe both update paths.
Related: #1931 (queue delay invalidates the dispatch side via base_sha drift — same family, other half), #1929/#1927 (dispatch actor), #1925 (CodeQL dispatch never succeeds).
Instrument notes for whoever re-measures: the pulls list endpoint omits merged_by; a check-run's started_at is null while queued; the closed event after merge creates fresh check-runs on the same head, so "latest check-run per name" reads post-merge state unless filtered by merged_at.
Summary
Under queue saturation, the scheduler's pre-review
update_branchpath discards in-flight checks. A scheduler run waits 2h+ for a runner, executes aftermainhas moved, picks one behind PR from its scan — its own PR (#1926) or another one (acodex/originweave-mv3-ownerrun updated #1857) — and mergesmaininto that head before any review is dispatched. Every completing run therefore has one near-random victim, and the scheduler's completion rate is the PR cancellation rate. The push cancels every queued check on the old head and puts the new head at the back of the queue.mainmoves roughly every 28 minutes (77 merges into this repository in ~36h), so no PR head survives long enough for its 12 required contexts to complete.Measured on
#1926(2026-09-05):33963177129,pull_request_target, created 11:22:53Z, completed 13:44:41Z (success)c730ae0b(Merge branch 'main' into …)PR #1926: update_branch: current head has no OpenCode approval; branch is outdated before review dispatch; branch update requested with OpenCode app token inside GitHub Actions as OpenCode GitHub App7920a70bSame mechanism observed on
#1812(c9244ceb, 13:31:29Z) and, after the#1926merge, on#1484(777ee3ed, 13:58:42Z: 21 of 30 check-runs on the old head cancelled at the push, 8 successes orphaned, new head starts from zero). The pushes are not a burst caused by the merge: they are a steady drip, one per PR scheduler run that finishes its queue wait, and withmainmoving every ~28 minutes almost every run finds its PR behind. The 5–12 bot-pushed branches seen in the 20 minutes after6f8c51d7are the scheduler runs that happened to complete in that window.Consequence on merge governance
Reconstructing the check state at
merged_atfor every PR merged into.githubsince 2026-09-04 (77 PRs, RESTcommits/{sha}/check-runs, only runs completed beforemerged_atcounted,skippedcounted as satisfied):Depth ≥ 2 jobs (
opencode-review,coverage-evidence,trivy-fs,scorecard,osv-scan,dependency-review, bothCodeQL compatibility analysis (...)) had no check-run at all before merge on 75–77 heads: their upstream gate job never got a runner. Every merge since 09-04 was therefore an admin bypass with the required review evidence absent.merged_byis populated on these, so it is not a bypass signal; the check state atmerged_atis.Where
.github/workflows/pr-review-merge-scheduler.yml:139—UPDATE_BRANCHESistrueforpull_request_targetruns, so every PR's own event run performs the update.scripts/ci/pr_review_merge_scheduler_core.py:4774-4785—if behind_by and trigger_reviews:→update_branch; there is no check for required check-runs still queued/in progress on the current head.CLAUDE.mdstates the scheduler "updates a PR branch only when the latest review is approved, no current-head check has failed, and GitHub reports the PR as behind". The pre-review path above is a second, undocumented update path.Proposed fix (smallest root-cause change)
pr_review_merge_scheduler_core.py, before the pre-reviewupdate_branch, decidewaitwhen the current head still has required check-runs inqueued/in_progress— do not discard evidence in flight. One helper, one guard, one contract test.UPDATE_BRANCHEStofalseforpull_request_targetruns and leave the refresh to theschedule/pushsweeps, which are rate-limited byBRANCH_UPDATE_LIMIT.CLAUDE.mdsentence to describe both update paths.Related: #1931 (queue delay invalidates the dispatch side via
base_shadrift — same family, other half), #1929/#1927 (dispatch actor), #1925 (CodeQL dispatch never succeeds).Instrument notes for whoever re-measures: the pulls list endpoint omits
merged_by; a check-run'sstarted_atis null while queued; theclosedevent after merge creates fresh check-runs on the same head, so "latest check-run per name" reads post-merge state unless filtered bymerged_at.