Repository navigation
CI runs once per pull request, in the merge queue - #860
Conversation
The owner's ruling. A pull request's run did the merge queue's work a second time: `gh pr ready` started it, auto-merge waited for it, and the queue then ran the same tree again. Over the last 89 ci.yml runs the pull request runs took 1651 job-minutes against the queue's 2164. On `pull_request` every check now reports skipped, which GitHub reads as success for a required check, so a pull request still enters the queue. The skip sits on the job that names each check: `host` in ci.yml, `build` in toolchain.yml and `suite` in guest.yml. A skipped caller of a reusable workflow reports its own name (`toolchain`, `guest`), never the callee's `toolchain / build` or `guest / suite`; a draft's run on this repository shows exactly that, so a caller-level skip would leave the required `guest / suite` waiting forever. The `types:` list goes: no run does work on a pull request, so a draft marked ready needs no fresh one. Caches: a pull request's entries were scoped to its merge ref and served only that pull request's re-runs; the queue's ref is its own branch, read by no other. Main's entries are written by publish.yml's toolchain on every push to main and by the nightly, as before, and every queue run reads them. Root CLAUDE.md, reviewer.md's two workflow rules and one issue's text named the pull request's run; they say what now holds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
The owner's request: a filed track, added unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Review of #860 at 5c0bee9 (base 57616a0 = origin/main). Net: +49 −10 over 7 files. Production (workflows) is +10 −3, tests +0, prompts and root Verified at head:
BLOCKER
NOTE
Watch
SEND BACK |
…t rule names its if: exactly A draft opened under the old workflow carries only the callers' names (`host`, `toolchain`, `guest`), never `guest / suite`; without a run on `ready_for_review` its required check would stay expected and auto-merge would never enqueue it. The run costs about a second. The concurrency group governed only one-second skip runs and merge-group refs that are unique per group, so it cancelled nothing. reviewer.md's guest rule now names the one `if:` it admits, so an `if:` that would skip merge_group stays a BLOCKER. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Review of #860, round 2, at 259b712 (base 57616a0 = origin/main). Net: Earlier BLOCKERs
Earlier NOTEs
Verified at head
BLOCKERNone. NOTENone. WatchThis is unchanged from round 1. On the first LAND |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
What changed
CI runs once per pull request, in the merge queue (the owner's ruling).
ci.yml: onpull_requestevery check reports skipped; onmerge_groupthe full run happens as before.types:keepsready_for_review: a draft opened under the old workflow carries only the callers' names (host,toolchain,guest) and noguest / suite— User programs use AVX to AVX-512, saved by the best XSAVE form each CPU offers #859's head is one — and only a run atgh pr readygives it the required name, for about 1 s.if: github.event_name != 'pull_request'on the job that names each check:hostinci.yml,buildintoolchain.yml,suiteinguest.yml. The callers inci.ymllose theirif:;guestkeeps!cancelled(), so it still runs whatevertoolchainconcluded.ci.yml'sconcurrencygroup goes: it now governed only 1-second skip runs and merge-group refs unique per group (gh-readonly-queue/main/pr-N-<sha>), so it cancelled nothing.toolchain.yml's cache saves are unchanged: they already skipmerge_group, and main's writers already exist (below).CLAUDE.md(thehostcheck sentence),reviewer.md's two rules that named the draft skip and "guest / suitehas no job-levelif:", andissues/ci-runs-the-whole-guest-suite.mdsaid a ready pull request runs CI; they now say what holds.reviewer.mdnames the oneif:it admits exactly: "hostskips only apull_requestrun" and "guest / suite's only job-levelif:isgithub.event_name != 'pull_request'", so anif:that would skipmerge_groupstays a BLOCKER. The reviewer.md edit changes the bar: the rule it replaces forbade exactly this design, so it is flagged here for the orchestrator.issues/general-crates-are-published-and-toyos-consumes-them.md, the owner's filed track, added unchanged.No repository setting changes. The
mainruleset requireshostandguest / suite(read withgh api repos/:owner/:repo/rulesets/20589156);toolchain / buildis not required, and still reports.Why the skip sits in the called workflow
Measured on this repository, not assumed: commit of run 38154975456 (a draft's run, every caller skipped) carries check runs named
host,toolchain,guest, allskipped, and notoolchain / buildorguest / suite. A skipped caller of a reusable workflow reports its own name. A caller-level skip would leave the requiredguest / suite"Expected — waiting" forever and the pull request could never enter the queue.GitHub's documentation (Using conditions to control job execution): "A job that is skipped will report its status as "Success". It will not prevent a pull request from merging, even if it is a required check."
Caches
GitHub's documentation (Dependency caching, Restrictions for accessing a cache): "Workflow runs can restore caches created in either the current branch or the default branch", and "When a cache is created by a workflow run triggered on a pull request, the cache is created for the merge ref (
refs/pull/.../merge) … It cannot be restored by the base branch or other pull requests." A merge group runs on its own branchgh-readonly-queue/main/pr-N-<sha>, so its entries are read by that run alone (itsguestrestores the sysroot) — which is whytoolchain.ymlalready does not save LLVM, compiler or freestanding there.Measured, #829: its
pull_requestrun 38077784159 (job 114288273049) loggedCache restored from key: toolchain-llvm-cc80e6c5b01b2cf1at 2026-10-10T18:57:59Z; its queue run 38079207502 (job 114292455854) loggedCache not found for input keys: toolchain-llvm-cc80e6c5b01b2cf1at 19:19:30Z, 21 min later, andtoolchain / buildtook 120 min. Read withgh run view --job <id> --loginto a file, thenrg -n toolchain-llvm-cc80e6c5b01b2cf1over it.So the pull request's run never filled a cache any later run could read: its entries served only that pull request's re-runs. The caches every run reads are main's, and main already writes them:
publish.yml'stoolchainon everypushto main (the comment "Main's two callers" intoolchain.yml), and the nightly. In the 29 push-to-mainpublishruns since 2026-10-10T12:23Z,toolchain / buildtook 1-9 min except one, 38087976075, 100 min: the push after a toolchain-changing landing, rebuilding what the queue had built but could not hand on.What still rebuilds on a miss main could not have filled: a queue run for a toolchain-changing pull request misses (main does not hold the key yet) and builds LLVM/compiler (85-131 min in runs 38079207502, 38082453733, 38083680546, 38083950661, 38087063624, 38090097188), and the push to main after it builds again. Both were true before this change too; the change removes only the third build, the pull request's (78 min in 38041261564, 23 min in 38158218435).
Roast
Minutes saved. The 89
ci.ymlruns with at least one non-skipped job created 2026-10-10T08:39Z to 2026-10-11T17:51Z (about 33 h): 46pull_requestruns took 1716 job-minutes, 43merge_groupruns 2228. This change removes the 1716, 43.5% of ci.yml's minutes. The command:prints
merge_group43 runs 2228.35,pull_request46 runs 1715.88. The previous round's 1651 / 2164 was not reproduced by this command and is withdrawn.It also removes one full run from every landing's wall time: the orchestrator's
gh pr readystarted the pull request's run, auto-merge waited for it, and only then did the queue run the same tree (e.g. 38158218435 17:05-17:50, then queue 38161336035 17:50-18:40). That is 20-45 min of latency per landing gone.Queue failures found later. The pull request's run was the filter before the queue. In the window it caught 1 red (38055234245) in 46 runs; the queue still saw 2 reds in 43 (38067686940, 38051549768) with that filter in place (runs in the window whose
conclusionis notsuccess, from the sameruns.json). A red found in the queue costs more than one found on the pull request: the group is ejected, and withmax_entries_to_build: 5andALLGREENevery group built behind it re-runs, up to 4 runs at 2228 / 43 = 51.8 job-minutes each, so at most 4 × 51.8 = 207 job-minutes per red that slips. Against that, the pull request's run cost 1716 / 46 = 37.3 job-minutes per landing. Break-even: 37.3 / 207 = 0.18, so the trade holds unless the pull request's run would catch a red in more than about 1 landing in 5.6; it caught 1 in 46 here. (jq -n '1715.8833/46, 2228.35/43, 4*2228.35/43, (1715.8833/46)/(4*2228.35/43)'prints 37.30, 51.82, 207.29, 0.180.) It rests on what already carries a review: the implementer's own--ci hostand guest-test exit codes in the body.What is lost. No check on a pull request means a reviewer sees no CI run there; the body's recorded exits are the evidence, as
orchestrator.mdalready has it ("its review rests on the exit codes its body records"). A pull request whose author did not run a gate is caught by the queue, at the queue's price.The rule it bends.
reviewer.mdforbade a job-levelif:onguest / suitebecause a skipped required check is green. That hazard is now deliberate and narrow: the only skip isevent_name != 'pull_request', the rule now names that condition exactly, andmerge_group— the run that decides a landing — never takes it. The caller's!cancelled()stays, so a failedtoolchainin the queue still redsguest / suiteon its cache miss.Gates
cargo run -- --ci hostat head 259b712: EXIT=0 (log posted whole in 9 comments: 1 2 3 4 5 6 7 8 9). It includesci::tests::the_required_check_is_a_job_on_every_pull_requestandworkflows_run_against_main_on_hosted_runners, which read the workflows as text, andsourcegate's walk of every tracked file, the issue file included.--build-onlyis untouched by it and was not run.pull_requestrun (below) and the queue run.This pull request's own run
Run 38167031171 (
pull_request, head 259b712): created and completed 19:12:28Z, 0 job-minutes. Check runs on the head commit (gh api repos/ToyOSOrg/ToyOS/commits/259b712980d8ce05afb2c8409c03b64c49cc7a6b/check-runs?per_page=100):hostskipped,toolchain / buildskipped,guest / suiteskipped — the three names, the callee's names included.gh pr view 860 --json statusCheckRollup,mergeStateStatus: all three COMPLETED/SKIPPED, merge stateCLEAN.What to watch
The queue run of this pull request:
host,toolchain / build,guest / suiteall run (not skipped) and concludesuccess;hostrestoreshost-sealed-Linux-X64-…;toolchain / build's restore steps hit LLVM, compiler and freestanding (main holds the keys; no toolchain source changes here), so it takes the usual 2-10 min, and total about 35-50 min as before. Any of the threeskippedthere means this landed untested: revert at once.The next push-to-main
publishrun: itstoolchain / buildhits every cache (1-3 min) and saves nothing new. The next nightly:host(seal) andtcg / suiterun. Then the next pull request after this lands: itspull_requestrun showshost,toolchain / build,guest / suiteskipped within seconds, and auto-merge admits it to the queue. #859 getsguest / suiteon its head, byready_for_reviewor a push, before it can enter the queue.Unsure
host,toolchain,gueston its head, and noguest / suite(User programs use AVX to AVX-512, saved by the best XSAVE form each CPU offers #859 today). Keptready_for_reviewgives it the three callee names atgh pr ready; I have not watched that happen on such a draft, only reasoned it from this pull request's ownsynchronizeruns, which produce the three names.🤖 Generated with Claude Code
https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C