Repository navigation
perf(pr-merge): start one full run inside the cap for uncovered lane merges - #10853
Conversation
…merges The lane arms nothing past merge.max-in-flight until a full run of main verifies the merges in flight, and main's full runs are capped at one per 30 minutes, so a batch that landed inside the cap waited it out. A new post-merge-dispatch step lets the cap give way when the post-merge report lists an unverified lane merge that no started full run covers: one run of main's tip for the whole batch. merge.full-run-cap-bypass (default true) turns it off. dispatch-full-run keeps the cap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
claude-security-review reviewed No security findings. critical-findings: 0 I read the full diff (6 files) and the PR context.
Spend, not a hold: the change can start up to one extra full run of main per 15-minute tick (about +48 a day). That uses runner minutes and org job concurrency on a public repository, with no billed resource, and I did not run the tests or the workflow. This is a read-only review. |
|
claude-review reviewed No findings. I read the full diff and the PR context, and checked the new step against What I checked:
Spend: this adds at most one extra full run per 15-minute tick on a public repository, which is runner minutes only, and no paid plan or billing setting changes. This is not a hold. Trusted path |
|
Owner decision record (R19). In the poteto session on 2026-10-29 at about 03:05Z, the owner answered my hold on this cap with "REMEMBER:" and re-pasted the standing direction, including:
The session applied it to this cap. #10491's basis was runner minutes, which are not cost on this public repo. The bypass costs about 0 GITHUB_TOKEN calls (full runs measured |
No related issue: a lane-throughput fix the coordinating session decided under the owner's standing direction on 2026-10-29. No issue tracks it.
Hold path:
.github/workflows/pr-merge.ymlis a hold path, so the owner approves the merge. No permission, secret, token or trigger change.Decision and its basis. The coordinating session relayed this decision under the owner's standing direction, re-pasted 2026-10-29 at about 03:05Z:
Quoted from the relay: "build the full-run cap bypass now ... #10491's basis was runner-minutes, which §3 no longer counts as cost, and the cap now limits lane throughput (arming stops at max-in-flight until a full run verifies the batch)." The 30-minute cap came from #10491 (efficiency plan item 5), which cut main's full runs to at most 48 a day to save runner minutes.
Reversible: set
merge.full-run-cap-bypass: falseindocs/conventions/pr-pipeline.yaml.Summary
The lane arms nothing past
merge.max-in-flight(5) until a full run of main verifies the merges in flight.post-merge-dispatchstarts at most one full run per 30 minutes (CAP_MINUTES). A batch that lands just after a run started therefore waits out the cap, then the next 15-minute tick, then the run itself.The coordinating session's measurement, UNVERIFIED by me: 5 merges at 00:51, and the room still read 5/5 at 01:23:38 (run 40799692656). That is about 55 minutes per 5 merges.
With this change, the first tick after the batch lands starts one run of main's tip inside the cap. The batch is verified when that run ends.
Fix
New step,
Find lane merges no full run covers. It runs inpost-merge-dispatchonly when the cover read reportscappedand the post-merge check judged (exit 0 or 5). It setsbypass=truewhen the post-merge report lists an unverified lane merge that meets both conditions:pendingholdsdispatch-run-pending, and nodispatch-run-in-progress:orattribution-pending:entry;.basecheckout answers containment.It fails closed toward the cap:
Once a run starts, the next tick finds it in the listing, so the same batch never starts a second run.
Changed condition on
Start the full CI run on main:(capped != 'true' || bypass == 'true'), andbypassalso counts as a reason to dispatch. Kill switch, report-only and rate-limit stops gate the dispatch exactly as before.Cover step, in both jobs: it writes its listing to
$RUNNER_TEMP/cover-runs.json. The two bodies stay identical, as their test requires.Config: new key
merge.full-run-cap-bypassinpr-pipeline.yamland its schema, defaulttrue, absent read astrue. It is placed besidereport-only, away frommax-in-flight, which a separate PR raises from 5 to 10.Unchanged:
dispatch-full-runkeeps the cap. It has no post-merge report to name lane merges, and its sparse checkout has no history to judge coverage. The post-merge-dispatch tick, 15 minutes at most, covers its batches.pr-merge-verify-mainand pr-require-checks'pr-require-checks-main-dispatch, neither cancelling in progress) still collapse a burst to one run going and one waiting.workflow_runtrigger. A full run started by GITHUB_TOKEN raises no such event. Since 2026-10-28T06:00Z, 1 of 33 completed bot-dispatched full runs had a post-merge-demotion-alertworkflow_runrun within 3 minutes.Verification
node --test .github/scripts/pr-merge/workflow.test.mjs .github/scripts/pr-merge/merge-job.test.mjspasses: 172 tests, 0 failures. The new step's script runs against a real git history (BEFORE, MERGED, TIP, plus a SHA the checkout lacks) and a report file. The expected values come from the rule above:bypassonce;falsekeeps the cap, and an absent key means on;cappedplusbypass, and not under report-only or a rate-limited read.ifwithout the bypass.actionlintis clean,scripts/check-convention-yaml.shvalidates all 11 convention YAML files, andscripts/check-changelog-fragments.sh --checkpasses.Cost:
pr-require-checksworkflow_dispatchruns in the 2026-10-28 ledger loggedgithub-token=0.Related
merge.max-in-flightfrom 5 to 10. This PR touches only its new key inpr-pipeline.yaml.enable-auto-merge.mjsand the lane-room reader, which this PR does not touch.🤖 Generated with Claude Code