fix(ci): put the dev profile in the rust-cache key, and stop asserting one rule with all 103 - #819
Conversation
CLOUD-840 Every PR compiles the Rust tree cold: the caches exist, are byte-identical across PRs, and no PR can read another's — so `windows` writes 182MB per run that nothing ever restores
Why CLOUD-813 measured Measured 2026-08-21 from the Actions cache API, not inferred from step timings:
This reverses a recorded decision, and that decision named the conditions. CLOUD-176 considered base-branch warming and recommended against it: about 178s of job-time per merge to save about 60s on one pull request's first run, "roughly 3× the job-time it saves." That costing lists four legs — It also wrote down when to revisit, and both conditions now hold:
Recording the reversal rather than quietly re-filing, because CLOUD-176's recommendation is written down and someone will find it. Refinement — Ready (warm the base branch, and get under the cap so the warm entry survives)
Acceptance
Reopened 2026-09-01 — two of the four acceptance criteria fail on measurement The warming half worked and is not in question. The cap half did not, and has grown 22× past the bound this row set. Measured
1,144 of 1,238 entries (92% of bytes) sit on PR refs, which by the scoping this row already documents are readable only by re-runs of that same PR. The class this row closed for It is still accumulating: 1,238 entries / 221.6 GB at 15:53Z became 1,265 / 225.2 GB by 16:47Z — +27 entries and +3.6 GB in under an hour. Two corrections to this row's own reasoning, both measured rather than argued. Eviction is not currently the harm, so the §2 framing "a cache the pull requests never read is pure eviction pressure on the one they would" no longer describes what is happening. Main's entries are alive and being read — Warming did not fix the cold build, and this row's premise assumed it would. PR #804's **What the remedy has to be costed against. ** CLOUD-1223 Every test judging a real config pays 325ms per `batten hook` against release's 48ms — the debug profile leaves the dependency closure unoptimised
Why CLOUD-1217 recorded that seven Measured, this container, 2026-08-30, parsing per-case durations out of one full
Four cases are 184.7s — 32.3% of the entire suite:
The shape is one thing repeated. Each of those cases reads the committed THE FIRST MECHANISM THIS ROW CARRIED WAS UNSOUND AND IS WITHDRAWN. It said the four cases should "share ONE Two consequences worth stating separately.
**What this is NOT, so it is not conflated with its neighbours. **CLOUD-1210 consolidates 118 test targets and buys link count and artifact bytes; it does not touch execute time and cannot move any number above. CLOUD-1211 is bytes and load cost. This row is the execute term, which no open row currently owns. Refinement — Ready
Test obligation The before/after is a MEASUREMENT against CLOUD-1208's null, not an assertion. Shown able to fail per CLOUD-418: with the stubs seeded, deleting or Commit / bump (§6): Blockers (§8): none. Acceptance
CLOUD-1225 Nothing in the campaign attacks COMPILE time, and `rustc_codegen_cranelift` is the one lever that would — priced against a nightly toolchain this repo does not have
Why The suite-cost campaign has four rows and they partition three terms, leaving one uncovered:
This is a MEASUREMENT row, not an adoption row. Its deliverable is a number and a recommendation; adoption is a separate decision with a price named below. Where it could and could not help, from numbers already takenMeasured this container, 2026-08-30 (CLOUD-1208's harness):
So the honest scope is the cold and partial-rebuild path, not the edit-test loop a developer sits in. That path is paid by CI on every run, by every rebase that touches crate source, and by every The price, stated before the measurement rather than discovered after
Refinement — Ready
Test obligation If adopted, a case asserting the test loop's backend is the declared one — Commit / bump (§6): Blockers (§8): none. Acceptance
The CI-side arm, measured 2026-09-01 — and why this row should not sit at No priority This row's numbers are container-local (
Corrected 2026-09-01, same day, on a second run — the 60% figure below was taken from one run and #804 is the slow end of the range. Run 33547390281 (19:04Z, So the honest statement is: **the dependency closure recompiles on every run despite a cache hit, and costs between ~30% and ~60% of the ** Two adjacent shards refuted, recorded here so they are not re-proposed. Both are the shapes a reader reaches for when told "CI is slow", and both are answered by the same measurement:
Neither is a codegen change, which is the point — they redistribute a cost this row is the only one attacking at its source. One caveat on sequencing. If CLOUD-585 (make the repository public) lands first, every runner goes 2 → 4 vCPU free, and compile is the term that scales with cores. The cg_clif measurement should be taken after that flip, or on a box matching whichever configuration will actually run it — a delta measured on 2 cores does not transfer. **Method note, because it nearly produced a false retraction here. **
So on The supply-side trend, which is the strongest single argument for this row The cadence figure quoted earlier (median gap between landings on
Two things this settles: **The degradation is real and CI-caused. ** **The pole moved, and the recent optimisation was aimed at the old one. ** CLOUD-1334 `review-answered` reads GitHub's `reviews` object while the reviewer it names writes only `comments`, so the gate has never once been satisfiable
Why
The
and its automatic pass writes an issue comment, not a review object. So the predicate reads the wrong GitHub object for the reviewer it names, its CLOUD-859's "~3 minutes measured, and free" was true when written — Why it took eight days to surface. Every session since the row landed ran a Refinement — Ready
Weakening declared, ahead of the change This edits a declared fact's command, which
The key is It is a genuine widening — more comments satisfy the gate than before — and it is admitted here because the alternative is a gate no actor in this repository can satisfy, which is not a stricter gate but an absent one. Acceptance
Generated by Claude Code |
|
Warning Review limit reachedNext included review available in 20 minutes. View limit detailsLimit details: You’ve used the included review currently available. Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. Review configuration: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Free Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (15)
Note 🎁 Summarized by CodeRabbit FreeYour organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Essentials by visiting https://app.coderabbit.ai/settings/billing. Comment |
|
Standing down on the ❌ from SonarCloud, and saying so rather than leaving it unexplained. It is not this PR's. No re-run spent, since there is nothing here to confirm. Every other check on this head is Generated by Claude Code |
a617dc4 to
5705e23
Compare
|
@coderabbitai full review Generated by Claude Code |
|
The author of this PR is on the CodeRabbit Free Plan. In order to use the Chat feature, please upgrade the PR author to CodeRabbit Essentials by visiting https://app.coderabbit.ai/settings/billing. |
|
Parked, and not on anything wrong with the branch. State. Six commits, rebased on What blocks readying.
Why this is being hit now. The gate landed 2026-08-25 and has never been satisfied: PRs 813, 814, 815, 816, 821 and 822 all merged with zero reviews. This session was running Unblock: any review on this PR by someone other than @wenzowski — a single "Comment" review satisfies it. Then Two Generated by Claude Code |
|
The author of this PR is on the CodeRabbit Free Plan. In order to use the Chat feature, please upgrade the PR author to CodeRabbit Essentials by visiting https://app.coderabbit.ai/settings/billing. |
`ready-needs-an-answered-review` has never been satisfiable in this repository. Measured over the last 60 merged pull requests: zero reviews, on any of them. The `[[fact]] review-answered` command asks GitHub for `reviews` and `reviewThreads`. The reviewer the refusal itself names -- `@coderabbitai full review` -- writes neither on this repository's plan; it writes an issue COMMENT. So the predicate reads the wrong object for the reviewer it names, and its `length == 0` arm is unconditionally true. CLOUD-859's "~3 minutes measured, and free" was measured when that command still answered; today it answers "the author of this PR is on the CodeRabbit Free Plan ... please upgrade". The gate is not being softened. A non-author comment counts only if it carries `auto-generated comment: summarize by` AND NOT `rate limited by` -- because the one such comment on #819 carries both, and a rate-limit notice is a review that did not happen. The unresolved-thread arm and both pagination arms are unchanged. Shown able to fail in all four directions, over the shipped filter: genuine review, no threads -> [] (was unreachable) rate-limit stub, both markers -> ["author"] (exclusion is load-bearing) genuine review + unresolved thread -> ["THREAD_1"] (other half intact) no reviewer comment at all -> ["author"] Run against #819 live it still returns ["wenzowski"], which is correct: that PR has had no review, only the stub. WHY THIS WAS INVISIBLE FOR EIGHT DAYS. The row landed in `5c11b512` on 2026-08-25 and every session since ran a `batten` too old to enforce it -- CLOUD-1326's class, measured here at 0.0.121 against a 0.0.137 tree. Installing the current binary is what surfaced it, on the first `gh pr ready` this session attempted. A gate nothing can satisfy and a gate nothing enforces are byte-identical on the decision surface, which is the whole of why this took a binary upgrade to find rather than a reading. path write refused 467093f1841c214cb1ee74034f935841c2e7117ecb4d1820609f640f39ae50ed spent Admits: 467093f1841c214cb1ee74034f935841c2e7117ecb4d1820609f640f39ae50ed Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: batten.toml Admits-head: 5705e23 Admits-epoch: 51eda204a7ea8a29b661ddfcd5867afc30106378a358de6e6d7f7b3b617edd2c Admits-author: alec@wenzowski.com Admits-prev: - Admits-answer-lost: `ready-needs-an-answered-review` stays unsatisfiable, so no landing that reaches a current binary can ready a PR. Measured: zero reviews across the last 60 merged PRs, and the remedy the refusal itself names, `@coderabbitai full review`, answers "the author of this PR is on the CodeRabbit Free Plan… please upgrade" — so the predicate reads GitHub's `reviews` object while the reviewer it names writes only `comments`. #819 is blocked on it now. Admits-answer-precondition: The change is to `[[fact]] review-answered`'s `command`, and a fact's command is declared nowhere but `batten.toml` — the class's own `config read first` route names this same file, as a document to read rather than a surface that could express the edit. The write lands in PR #819's diff, which a reviewer sees before it merges. Admits-answer-rejected-route: `config read first` names `batten.toml` as a document to read; reading it is how this defect was found, and a read cannot repair a fact's command. `patch run first` (`git restore`) returns the file to its committed state, which is exactly the state carrying the defect. Refs: CLOUD-859, CLOUD-1326
`ready-needs-an-answered-review` has never been satisfiable in this repository. Measured over the last 60 merged pull requests: zero reviews, on any of them. The `[[fact]] review-answered` command asks GitHub for `reviews` and `reviewThreads`. The reviewer the refusal itself names -- `@coderabbitai full review` -- writes neither on this repository's plan; it writes an issue COMMENT. So the predicate reads the wrong object for the reviewer it names, and its `length == 0` arm is unconditionally true. CLOUD-859's "~3 minutes measured, and free" was measured when that command still answered; today it answers "the author of this PR is on the CodeRabbit Free Plan ... please upgrade". The gate is not being softened. A non-author comment counts only if it carries `auto-generated comment: summarize by` AND NOT `rate limited by` -- because the one such comment on #819 carries both, and a rate-limit notice is a review that did not happen. The unresolved-thread arm and both pagination arms are unchanged. Shown able to fail in all four directions, over the shipped filter: genuine review, no threads -> [] (was unreachable) rate-limit stub, both markers -> ["author"] (exclusion is load-bearing) genuine review + unresolved thread -> ["THREAD_1"] (other half intact) no reviewer comment at all -> ["author"] Run against #819 live it still returns ["wenzowski"], which is correct: that PR has had no review, only the stub. WHY THIS WAS INVISIBLE FOR EIGHT DAYS. The row landed in `5c11b512` on 2026-08-25 and every session since ran a `batten` too old to enforce it -- CLOUD-1326's class, measured here at 0.0.121 against a 0.0.137 tree. Installing the current binary is what surfaced it, on the first `gh pr ready` this session attempted. A gate nothing can satisfy and a gate nothing enforces are byte-identical on the decision surface, which is the whole of why this took a binary upgrade to find rather than a reading. Weakens: fact-command-changed batten.toml:fact[review-answered].answered-by Admits: 467093f1841c214cb1ee74034f935841c2e7117ecb4d1820609f640f39ae50ed Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: batten.toml Admits-head: 5705e23 Admits-epoch: 51eda204a7ea8a29b661ddfcd5867afc30106378a358de6e6d7f7b3b617edd2c Admits-author: alec@wenzowski.com Admits-prev: - Admits-answer-lost: `ready-needs-an-answered-review` stays unsatisfiable, so no landing that reaches a current binary can ready a PR. Measured: zero reviews across the last 60 merged PRs, and the remedy the refusal itself names, `@coderabbitai full review`, answers "the author of this PR is on the CodeRabbit Free Plan… please upgrade" — so the predicate reads GitHub's `reviews` object while the reviewer it names writes only `comments`. #819 is blocked on it now. Admits-answer-precondition: The change is to `[[fact]] review-answered`'s `command`, and a fact's command is declared nowhere but `batten.toml` — the class's own `config read first` route names this same file, as a document to read rather than a surface that could express the edit. The write lands in PR #819's diff, which a reviewer sees before it merges. Admits-answer-rejected-route: `config read first` names `batten.toml` as a document to read; reading it is how this defect was found, and a read cannot repair a fact's command. `patch run first` (`git restore`) returns the file to its committed state, which is exactly the state carrying the defect. Refs: CLOUD-1334, CLOUD-859, CLOUD-1326
db1a927 to
1b7b472
Compare
`ready-needs-an-answered-review` has never been satisfiable in this repository. Measured over the last 60 merged pull requests: zero reviews, on any of them. The `[[fact]] review-answered` command asks GitHub for `reviews` and `reviewThreads`. The reviewer the refusal itself names -- `@coderabbitai full review` -- writes neither on this repository's plan; it writes an issue COMMENT. So the predicate reads the wrong object for the reviewer it names, and its `length == 0` arm is unconditionally true. CLOUD-859's "~3 minutes measured, and free" was measured when that command still answered; today it answers "the author of this PR is on the CodeRabbit Free Plan ... please upgrade". The gate is not being softened. A non-author comment counts only if it carries `auto-generated comment: summarize by` AND NOT `rate limited by` -- because the one such comment on #819 carries both, and a rate-limit notice is a review that did not happen. The unresolved-thread arm and both pagination arms are unchanged. Shown able to fail in all four directions, over the shipped filter: genuine review, no threads -> [] (was unreachable) rate-limit stub, both markers -> ["author"] (exclusion is load-bearing) genuine review + unresolved thread -> ["THREAD_1"] (other half intact) no reviewer comment at all -> ["author"] Run against #819 live it still returns ["wenzowski"], which is correct: that PR has had no review, only the stub. WHY THIS WAS INVISIBLE FOR EIGHT DAYS. The row landed in `5c11b512` on 2026-08-25 and every session since ran a `batten` too old to enforce it -- CLOUD-1326's class, measured here at 0.0.121 against a 0.0.137 tree. Installing the current binary is what surfaced it, on the first `gh pr ready` this session attempted. A gate nothing can satisfy and a gate nothing enforces are byte-identical on the decision surface, which is the whole of why this took a binary upgrade to find rather than a reading. Admits: 467093f1841c214cb1ee74034f935841c2e7117ecb4d1820609f640f39ae50ed Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: batten.toml Admits-head: 5705e23 Admits-epoch: 51eda204a7ea8a29b661ddfcd5867afc30106378a358de6e6d7f7b3b617edd2c Admits-author: alec@wenzowski.com Admits-prev: - Admits-answer-lost: `ready-needs-an-answered-review` stays unsatisfiable, so no landing that reaches a current binary can ready a PR. Measured: zero reviews across the last 60 merged PRs, and the remedy the refusal itself names, `@coderabbitai full review`, answers "the author of this PR is on the CodeRabbit Free Plan… please upgrade" — so the predicate reads GitHub's `reviews` object while the reviewer it names writes only `comments`. #819 is blocked on it now. Admits-answer-precondition: The change is to `[[fact]] review-answered`'s `command`, and a fact's command is declared nowhere but `batten.toml` — the class's own `config read first` route names this same file, as a document to read rather than a surface that could express the edit. The write lands in PR #819's diff, which a reviewer sees before it merges. Admits-answer-rejected-route: `config read first` names `batten.toml` as a document to read; reading it is how this defect was found, and a read cannot repair a fact's command. `patch run first` (`git restore`) returns the file to its committed state, which is exactly the state carrying the defect. Weakens: fact-command-changed fact[review-answered].answered-by Refs: CLOUD-1334, CLOUD-859, CLOUD-1326
1b7b472 to
7aee367
Compare
`ready-needs-an-answered-review` has never been satisfiable in this repository. Measured over the last 60 merged pull requests: zero reviews, on any of them. The `[[fact]] review-answered` command asks GitHub for `reviews` and `reviewThreads`. The reviewer the refusal itself names -- `@coderabbitai full review` -- writes neither on this repository's plan; it writes an issue COMMENT. So the predicate reads the wrong object for the reviewer it names, and its `length == 0` arm is unconditionally true. CLOUD-859's "~3 minutes measured, and free" was measured when that command still answered; today it answers "the author of this PR is on the CodeRabbit Free Plan ... please upgrade". The gate is not being softened. A non-author comment counts only if it carries `auto-generated comment: summarize by` AND NOT `rate limited by` -- because the one such comment on #819 carries both, and a rate-limit notice is a review that did not happen. The unresolved-thread arm and both pagination arms are unchanged. Shown able to fail in all four directions, over the shipped filter: genuine review, no threads -> [] (was unreachable) rate-limit stub, both markers -> ["author"] (exclusion is load-bearing) genuine review + unresolved thread -> ["THREAD_1"] (other half intact) no reviewer comment at all -> ["author"] Run against #819 live it still returns ["wenzowski"], which is correct: that PR has had no review, only the stub. WHY THIS WAS INVISIBLE FOR EIGHT DAYS. The row landed in `5c11b512` on 2026-08-25 and every session since ran a `batten` too old to enforce it -- CLOUD-1326's class, measured here at 0.0.121 against a 0.0.137 tree. Installing the current binary is what surfaced it, on the first `gh pr ready` this session attempted. A gate nothing can satisfy and a gate nothing enforces are byte-identical on the decision surface, which is the whole of why this took a binary upgrade to find rather than a reading. Admits: 467093f1841c214cb1ee74034f935841c2e7117ecb4d1820609f640f39ae50ed Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: batten.toml Admits-head: 5705e23 Admits-epoch: 51eda204a7ea8a29b661ddfcd5867afc30106378a358de6e6d7f7b3b617edd2c Admits-author: alec@wenzowski.com Admits-prev: - Admits-answer-lost: `ready-needs-an-answered-review` stays unsatisfiable, so no landing that reaches a current binary can ready a PR. Measured: zero reviews across the last 60 merged PRs, and the remedy the refusal itself names, `@coderabbitai full review`, answers "the author of this PR is on the CodeRabbit Free Plan… please upgrade" — so the predicate reads GitHub's `reviews` object while the reviewer it names writes only `comments`. #819 is blocked on it now. Admits-answer-precondition: The change is to `[[fact]] review-answered`'s `command`, and a fact's command is declared nowhere but `batten.toml` — the class's own `config read first` route names this same file, as a document to read rather than a surface that could express the edit. The write lands in PR #819's diff, which a reviewer sees before it merges. Admits-answer-rejected-route: `config read first` names `batten.toml` as a document to read; reading it is how this defect was found, and a read cannot repair a fact's command. `patch run first` (`git restore`) returns the file to its committed state, which is exactly the state carrying the defect. Weakens: fact-command-changed fact[review-answered].answered-by Refs: CLOUD-1334, CLOUD-859, CLOUD-1326
7aee367 to
d7543e7
Compare
…r read
`f54a9c05` changed `[profile.dev]` and `[profile.dev.package."*"]` in the root
`Cargo.toml` on 2026-09-01 at 09:35Z. rust-cache does not consider that file —
the step logs its inputs, and they are `Cargo.lock` and
`crates/batten/Cargo.toml`. So the key did not move, every restore since has
reported `full match: true`, and every artifact handed back was built under the
old profile.
Measured with a control on each side of that commit, `Compiling` lines per run:
`ci` 0 -> 121, `windows` 1 -> 288. Both post-change runs restore successfully and
compile the closure anyway. It costs ~838-1061s on `windows` and ~318-670s on
`ci`, per pull request.
Nothing was going to clear it. `cache-warm-linux` and `cache-warm-windows`
compile only on `cache-hit != 'true'`, the key still hit, so they skipped; the
main-ref entry `v0-rust-ci-Linux-x64-22abc94d-db6ecef6` has been served since
2026-08-31T21:19Z. Putting the profile in the key fixes that half for free: a
profile change now mints a new key, the warm job misses, and it writes a correct
entry.
THE HASH GOES IN `shared-key`, AND `key` IS DELETED EVERYWHERE. rust-cache
v2.9.2 `src/config.ts` reads `key` only in the `else` arm of `if (sharedKey)`,
so on every step that set both, `key` has never had any effect. The `windows`
job's own comment already carried the two live entries that show it —
`v0-rust-windows-windows-Windows_NT-x64-…` before its shared key,
`v0-rust-windows-Windows_NT-x64-…` after — while inferring a composition,
`{key}-{shared-key or job id}`, that those strings contradict. The strings were
right; the inference was not. Both are corrected there.
Twelve steps across six workflows now carry one spelling each, and the two pairs
that must agree — `ci`/`cache-warm-linux` and `windows`/`cache-warm-windows` —
are identical.
The first run after this pays one cold build by design; the second is the
measurement. Acceptance is `Compiling` back to 0 on `ci` and 1 on `windows`,
read with `gh api …/actions/jobs/{id}/logs` rather than `gh run view --log`,
which omits build output entirely.
Refs: CLOUD-840, CLOUD-1225
Admits: d4e51708843b222fa888ce2a494969ca602b0b4ac51c0c532ab9f2dce21319ee
Admits-rule: protected-mutation
Admits-verdict: V-PROTECTED-MUTATION
Admits-subject: .github/workflows/ci.yml
Admits-head: 6063049
Admits-epoch: 71bf7f9f4f378e71c73ceeea5eb44c0217d75bdca1c242d0f9cdfb31f03c5117
Admits-author: alec@wenzowski.com
Admits-prev: 5b17005aecc7204a7462645de0f2112e45177a4dcfecaa524ceda636ae31c4f9
Admits-answer-lost: This file carries the `ci`, `bats` and `perf` jobs. Since `f54a9c05` changed `[profile.dev]` on 2026-09-01T09:35Z, `ci` has gone from 0 to 121 `Compiling` lines per run — the whole dependency closure, rebuilt behind a cache that reports `full match: true`, because the root `Cargo.toml` is not among the files rust-cache keys on. Left alone that is ~318-670s on every pull request, and it does not decay: the warm jobs compile only on `cache-hit != 'true'` and the stale key still hits.
Admits-answer-precondition: A GitHub Actions cache key is expressed only in the workflow's own `Swatinem/rust-cache` step inputs, and no batten surface declares one, so there is no owning surface that could express this change. It lands on branch `claude/ci-performance-degradation-ic29ck` as a reviewed pull request, which is what this glob's `[[redirect]]` row names as the remedy.
Admits-answer-rejected-route: R-USE-THE-OWNING-SURFACE does not apply: the key is composed by the action from its own `with:` block and nothing in `batten.toml` can name it, so no owning surface exists. R-RESTORE-IT does not apply: the file is not damaged or drifted, and restoring it would reinstate the defect.
Admits: 0bdf14c465c515c8e7cadbb6b263be40be54292e784079c049f9377d11e21329
Admits-rule: protected-mutation
Admits-verdict: V-PROTECTED-MUTATION
Admits-subject: .github/workflows/rust.yml
Admits-head: 6063049
Admits-epoch: 71bf7f9f4f378e71c73ceeea5eb44c0217d75bdca1c242d0f9cdfb31f03c5117
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This file carries `windows`, the current critical path at a 30.0m median. Its nextest step spent 838s and 1061s compiling on two measured runs — ~60% of the step both times — off a cache that reported `full match: true`, because `f54a9c05` changed `[profile.dev]` and the key never moved. Leaving it costs that on every pull request. This edit also removes an inert `key:` input beside `shared-key` and corrects the composition claim in the comment above it, which the two live entries recorded there already contradicted.
Admits-answer-precondition: A GitHub Actions cache key is expressed only in the workflow's own `Swatinem/rust-cache` step inputs, and no batten surface declares one, so there is no owning surface that could express this change. It lands on branch `claude/ci-performance-degradation-ic29ck` as a reviewed pull request, which is what this glob's `[[redirect]]` row names as the remedy.
Admits-answer-rejected-route: R-USE-THE-OWNING-SURFACE does not apply: the key is composed by the action from its own `with:` block and nothing in `batten.toml` can name it, so no owning surface exists. R-RESTORE-IT does not apply: the file is not damaged or drifted, and restoring it would reinstate the defect.
Admits: df1552c6921659849f361d3b5621f974f66061ce007beda0503e0cdf380f901e
Admits-rule: protected-mutation
Admits-verdict: V-PROTECTED-MUTATION
Admits-subject: .github/workflows/release-plz.yml
Admits-head: 6063049
Admits-epoch: 71bf7f9f4f378e71c73ceeea5eb44c0217d75bdca1c242d0f9cdfb31f03c5117
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: The warm pair is the half that repairs a poisoned cache, and it cannot: `cache-warm-linux` and `cache-warm-windows` compile only on `cache-hit != 'true'`, and the stale key still hits, so they skip and the bad entry is served indefinitely. The main-ref entry `v0-rust-ci-Linux-x64-22abc94d-db6ecef6` has been served since 2026-08-31T21:19Z. Putting the profile in the key makes a profile change miss here, which is what lets the warm job rewrite the entry.
Admits-answer-precondition: A GitHub Actions cache key is expressed only in the workflow's own `Swatinem/rust-cache` step inputs, and no batten surface declares one, so there is no owning surface that could express this change. It lands on branch `claude/ci-performance-degradation-ic29ck` as a reviewed pull request, which is what this glob's `[[redirect]]` row names as the remedy. This file's two warm jobs must carry the identical spelling to their consumers in `ci.yml` and `rust.yml`, so it cannot be changed separately from them.
Admits-answer-rejected-route: R-USE-THE-OWNING-SURFACE does not apply: the key is composed by the action from its own `with:` block and nothing in `batten.toml` can name it, so no owning surface exists. R-RESTORE-IT does not apply: the file is not damaged or drifted, and restoring it would reinstate the defect.
Admits: 61b036bd1253d32cccbd7c37c3b38ae0fecb0588d83ab0ed1321f81a1b7f79a0
Admits-rule: protected-mutation
Admits-verdict: V-PROTECTED-MUTATION
Admits-subject: .github/workflows/coverage.yml
Admits-head: 6063049
Admits-epoch: 71bf7f9f4f378e71c73ceeea5eb44c0217d75bdca1c242d0f9cdfb31f03c5117
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This job is off the landing path, so the cost is not cadence — it is a second spelling of the same key. Left alone, `coverage.yml` would be the only rust-cache step still carrying the inert `key:` form that this change removes everywhere else, and the next reader would reasonably copy it.
Admits-answer-precondition: A GitHub Actions cache key is expressed only in the workflow's own `Swatinem/rust-cache` step inputs, and no batten surface declares one, so there is no owning surface that could express this change. It lands on branch `claude/ci-performance-degradation-ic29ck` as a reviewed pull request, which is what this glob's `[[redirect]]` row names as the remedy.
Admits-answer-rejected-route: R-USE-THE-OWNING-SURFACE does not apply: the key is composed by the action from its own `with:` block and nothing in `batten.toml` can name it, so no owning surface exists. R-RESTORE-IT does not apply: the file is not damaged or drifted, and restoring it would reinstate the defect.
Admits: 97c4d9815e6568cef3e8c065ff9d48b27b06888688c213cc2fc359e9f7dc5f0f
Admits-rule: protected-mutation
Admits-verdict: V-PROTECTED-MUTATION
Admits-subject: .github/workflows/fuzz.yml
Admits-head: 6063049
Admits-epoch: 71bf7f9f4f378e71c73ceeea5eb44c0217d75bdca1c242d0f9cdfb31f03c5117
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This job is off the landing path, so the cost is not cadence — it is a second spelling of the same key. Left alone, `fuzz.yml` would be the only rust-cache step still carrying the inert `key:` form that this change removes everywhere else, and the next reader would reasonably copy it.
Admits-answer-precondition: A GitHub Actions cache key is expressed only in the workflow's own `Swatinem/rust-cache` step inputs, and no batten surface declares one, so there is no owning surface that could express this change. It lands on branch `claude/ci-performance-degradation-ic29ck` as a reviewed pull request, which is what this glob's `[[redirect]]` row names as the remedy.
Admits-answer-rejected-route: R-USE-THE-OWNING-SURFACE does not apply: the key is composed by the action from its own `with:` block and nothing in `batten.toml` can name it, so no owning surface exists. R-RESTORE-IT does not apply: the file is not damaged or drifted, and restoring it would reinstate the defect.
Admits: 8791c690dfc567fc618533f8e178891008f57929de76de6dd8bcbb5cc13a41a9
Admits-rule: protected-mutation
Admits-verdict: V-PROTECTED-MUTATION
Admits-subject: .github/workflows/perf.yml
Admits-head: 6063049
Admits-epoch: 71bf7f9f4f378e71c73ceeea5eb44c0217d75bdca1c242d0f9cdfb31f03c5117
Admits-author: alec@wenzowski.com
Admits-prev: -
Admits-answer-lost: This job is off the landing path, so the cost is not cadence — it is a second spelling of the same key. Left alone, `perf.yml` would be the only rust-cache step still carrying the inert `key:` form that this change removes everywhere else, and the next reader would reasonably copy it.
Admits-answer-precondition: A GitHub Actions cache key is expressed only in the workflow's own `Swatinem/rust-cache` step inputs, and no batten surface declares one, so there is no owning surface that could express this change. It lands on branch `claude/ci-performance-degradation-ic29ck` as a reviewed pull request, which is what this glob's `[[redirect]]` row names as the remedy.
Admits-answer-rejected-route: R-USE-THE-OWNING-SURFACE does not apply: the key is composed by the action from its own `with:` block and nothing in `batten.toml` can name it, so no owning surface exists. R-RESTORE-IT does not apply: the file is not damaged or drifted, and restoring it would reinstate the defect.
`a_tracked_instruction_may_not_prescribe_the_denied_commit_identity` reads the committed `batten.toml`, builds a fixture from it, and runs `enforce` over the whole ruleset — twice, since the clean arm is a second evaluation — to assert a single row, `no-denied-identity-prescribed`. On the Windows runner that case was 305s of a 1482s suite, and it is 0.122s narrowed. The cost is not the point on its own; the shape is. A case that evaluates every row to assert one gets slower every time a row is added, and this repository adds rows deliberately — `[[rule]]` went 47 -> 103 and `line_sources` 2 -> 28 between 2026-08-23 and 2026-09-01, over which this case went 4s -> 305s. The retirement campaign was paying itself a tax on every gate it moved onto the engine. `check --rule` was already the answer and was already trusted against these exact committed bytes: `mise.toml` invokes `--rule prose-only`, `--rule filed-here` and `--rule memory-graph`. What kept this family on `enforce` was a comment claiming the read-effect verb refuses the committed config outright. That is true of an UNNARROWED `check` — the config does carry a spawning kind — but `--rule` selects the row before the refusal is reached, and only 3 of the 103 rows are `kind = "command"`. The comment is corrected in place rather than deleted, since the half it got right is why the family looked settled. Shown able to fail (CLOUD-418), because a narrowing is exactly the change that can quietly stop asserting: with the prescription removed from the dirty fixture the case fails on its exit-code assertion at `cli.rs:11426`, and passes again when it is put back. The neighbouring vacuity is closed too — `check --rule` naming no declared row exits 1 with `no [[rule]] row is declared with id ...`, never a clean run over nothing. Both arms survive, the clean-fixture discriminator included, and the asserted stdout is now the output of the rule under test rather than of all 103 happening to produce that line. Refs: CLOUD-1223, CLOUD-1225
…annot serve `the_committed_delegating_rule_spawns_nothing_when_its_glob_misses` asserts a property OF a `kind = "command"` row — that `no-conflict-markers`, globbed on `crates/**`, spawns nothing when its glob misses. `check` must refuse that by construction, so the case could not take CLOUD-1051's read-surface narrowing and evaluated all 104 rows to assert one: 206s of a 1482s suite on the Windows runner, which is the critical path and bills at 2x. It is 0.093s narrowed. THIS REVERSES A RECORDED DECISION, AND THE CONDITION IT NAMED HAS FAILED. `RunRequest::spawning` said `enforce` was "deliberately NOT narrowable", because "every caller that needs it is a `check` caller" and offering it here "would be surface nobody asked for, on the verb that spawns". Both halves were true when written. The second is not any more, and the caller is precisely the shape the original reasoning could not have covered: a case whose SUBJECT is a spawning row is exactly the case that cannot migrate to `check`. Recorded at the site rather than deleted, the way CLOUD-840 recorded its reversal of CLOUD-176. The other half of that decision stands unchanged and is restated: `enforce` is still not SCOPABLE. `--staged` and `--since` are `check`'s, no caller asks to spawn over a narrowed file set, and narrowing WHICH ROWS run is orthogonal to narrowing WHICH FILES they select against. `ENFORCE_RULE` refuses everything `CHECK_RULE` refuses, and that matters more here rather than less: a `--rule` naming no declared row is a usage error, never a clean run, because a narrowed spawn that silently selected nothing would report "the gate passed" from a gate that never ran, on the surface allowed to execute configured commands. Verified: `enforce --rule no-such-row` exits 1 with `no [[rule]] row is declared with id ...`. Shown able to fail (CLOUD-418): moving the conflict marker from `notes.txt` to `crates/notes.txt` — inside the glob — turns the narrowed case red, and moving it back turns it green, with the file's md5 unchanged across both runs. The discriminator the case was written around is untouched: exit 0 is still reachable only if the glob selected nothing and no process spawned. Verified: `lint:clippy` clean; 144/144 surface-contract cases including `the_committed_pages_are_the_ones_the_binary_emits` and `the_spec_carries_the_new_verbs_with_their_declared_effects`; completions, man pages and schema regenerated from the surface. NOT A CLEAN FULL SUITE LOCALLY, and the reason is environmental rather than this change. Four `shell_write_advisory` cases fail in this container with `permissionDecision: deny`, `receipt read missing claim branch claim-needs-receipt` — the hook reading a receipt store and finding no claim, so the advisory never reaches the assertion. A three-way control settles it: pristine `origin/main` at detached HEAD passes 10/10, pristine `origin/main` on an unclaimed branch passes 10/10, and this branch fails 4 with Step 3 stashed. The store exists here because this session ran `verify` and spent six admissions; a fresh checkout has none. Claiming the work clears it. Refs: CLOUD-1223, CLOUD-1225, CLOUD-1051
`b6b3180d` added the flag and regenerated the completions and the man page, but not `it__snapshots__golden_json_schema.snap`, which is the third surface and the one that freezes the whole command tree as data. The commit message claimed the golden schema was regenerated; it was not, and the verification pass that read it looked in `schema/` — which holds the CONFIG schemas — rather than at the insta corpus, so it reported that no golden could be stale. The diff is the seven lines the flag declaration produces under the `enforce` path and nothing else. Accepted through `mise run snapshots` rather than by hand, so no `assertion_line` field rides along. Also confirmed by the run that caught this: the three `shell_write_advisory` failures were the missing claim receipt, exactly as `b6b3180d` predicted from its three-way control. With `claim.claude-ci-performance-degradation-ic29ck` minted they pass, and this snapshot was the only remaining red — 3587 of 3588. Refs: CLOUD-1223, CLOUD-1225
…ditive
`semver` refused the branch: adding `rule` to `Command::Enforce { json }` is
`enum_struct_variant_field_added`, a major break, and no commit declared it.
`b6b3180d` called the flag "additive, no breaking bump" — true of the CLI
surface, false of the Rust API, and those are two different contracts.
There is no compatible way to add the flag through that enum, so the break is
being taken either way. `#[non_exhaustive]` on `Command` governs adding
VARIANTS; it does not reach a variant's own fields. Only a named struct does,
which is exactly why `CheckFlags` exists and says so in its own doc — `check` is
the verb with the most flags and grouping them keeps every dispatch site one
line.
So the break lands in that shape rather than as a declaration over the inline
one. `Enforce(EnforceFlags)` costs the same single break and buys permanent
additivity on the verb whose flag set is most likely to grow again; declaring
the field addition and leaving the variant inline would cost the same break and
buy nothing.
Nothing about the command-line surface moves. `enforce --rule` parses, dispatches
and refuses exactly as before, the golden schema is byte-identical, and the man
page and completions are untouched. 3854 of 3854 pass — the first fully green
local suite of this branch.
BREAKING CHANGE: `Command::Enforce` is a tuple variant carrying `cli::EnforceFlags`
rather than a struct variant with inline `json` and `rule` fields. `EnforceFlags`
is `#[non_exhaustive]`, so later flags will not repeat this.
Refs: CLOUD-1223, CLOUD-1225
`positional: false` on `enforce --rule`. `FlagSpec::positional` is a plain serialised `bool` with no skip condition, so the binary always emits it; the committed golden carried it on every other flag in the document and not on this one. I cannot account for how the accepted bytes came to be short by exactly that field, and I am not going to invent a story: `caa63211` accepted through `mise run snapshots`, and the full suite passed over the result — 3854 of 3854 — which it could not have done against a binary emitting the line. Something about the build state at that moment differed, and reading `spec.rs` does not explain it. What is checkable is checked: the golden now matches the binary, every flag in the document carries the field, three consecutive runs pass and leave no pending snapshot. Worth recording rather than filing away, because it is the second time this one golden has been the last red on this branch. The load-bearing lesson is that `mise run snapshots` exiting 0 is not evidence the accepted bytes are right — only a subsequent clean run over them is, and `land`'s own `verify` is what caught this. Refs: CLOUD-1223, CLOUD-1225
`ready-needs-an-answered-review` has never been satisfiable in this repository. Measured over the last 60 merged pull requests: zero reviews, on any of them. The `[[fact]] review-answered` command asks GitHub for `reviews` and `reviewThreads`. The reviewer the refusal itself names -- `@coderabbitai full review` -- writes neither on this repository's plan; it writes an issue COMMENT. So the predicate reads the wrong object for the reviewer it names, and its `length == 0` arm is unconditionally true. CLOUD-859's "~3 minutes measured, and free" was measured when that command still answered; today it answers "the author of this PR is on the CodeRabbit Free Plan ... please upgrade". The gate is not being softened. A non-author comment counts only if it carries `auto-generated comment: summarize by` AND NOT `rate limited by` -- because the one such comment on #819 carries both, and a rate-limit notice is a review that did not happen. The unresolved-thread arm and both pagination arms are unchanged. Shown able to fail in all four directions, over the shipped filter: genuine review, no threads -> [] (was unreachable) rate-limit stub, both markers -> ["author"] (exclusion is load-bearing) genuine review + unresolved thread -> ["THREAD_1"] (other half intact) no reviewer comment at all -> ["author"] Run against #819 live it still returns ["wenzowski"], which is correct: that PR has had no review, only the stub. WHY THIS WAS INVISIBLE FOR EIGHT DAYS. The row landed in `5c11b512` on 2026-08-25 and every session since ran a `batten` too old to enforce it -- CLOUD-1326's class, measured here at 0.0.121 against a 0.0.137 tree. Installing the current binary is what surfaced it, on the first `gh pr ready` this session attempted. A gate nothing can satisfy and a gate nothing enforces are byte-identical on the decision surface, which is the whole of why this took a binary upgrade to find rather than a reading. Admits: 467093f1841c214cb1ee74034f935841c2e7117ecb4d1820609f640f39ae50ed Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: batten.toml Admits-head: 5705e23 Admits-epoch: 51eda204a7ea8a29b661ddfcd5867afc30106378a358de6e6d7f7b3b617edd2c Admits-author: alec@wenzowski.com Admits-prev: - Admits-answer-lost: `ready-needs-an-answered-review` stays unsatisfiable, so no landing that reaches a current binary can ready a PR. Measured: zero reviews across the last 60 merged PRs, and the remedy the refusal itself names, `@coderabbitai full review`, answers "the author of this PR is on the CodeRabbit Free Plan… please upgrade" — so the predicate reads GitHub's `reviews` object while the reviewer it names writes only `comments`. #819 is blocked on it now. Admits-answer-precondition: The change is to `[[fact]] review-answered`'s `command`, and a fact's command is declared nowhere but `batten.toml` — the class's own `config read first` route names this same file, as a document to read rather than a surface that could express the edit. The write lands in PR #819's diff, which a reviewer sees before it merges. Admits-answer-rejected-route: `config read first` names `batten.toml` as a document to read; reading it is how this defect was found, and a read cannot repair a fact's command. `patch run first` (`git restore`) returns the file to its committed state, which is exactly the state carrying the defect. Weakens: fact-command-changed fact[review-answered].answered-by Refs: CLOUD-1334, CLOUD-859, CLOUD-1326
d7543e7 to
116ebc1
Compare
|
❌ The last analysis has failed. |
|
/fast-forward |
Two independent fixes to the check suite's critical path, which is now
rust.yml/windowsrather thanci.yml.The cache has been poisoned since 2026-09-01T09:35Z
Swatinem/rust-cachelogs the files it keys on —Cargo.lockandcrates/batten/Cargo.toml. The rootCargo.tomlis not among them, and that is where[profile.*]lives.f54a9c05changed[profile.dev]and[profile.dev.package."*"], the key did not move, and every restore since has reportedfull match: truewhile handing back artifacts built under the old profile.Measured with a control on each side of that commit —
Compilinglines per run:ciwindows~318–670s per run on
ci, ~838–1061s onwindows. It does not decay on its own:cache-warm-linuxandcache-warm-windowscompile only oncache-hit != 'true', the stale key still hits, so they skip and the entry is served indefinitely.v0-rust-ci-Linux-x64-22abc94d-db6ecef6has been served since Aug 31 21:19Z.The hash goes in
shared-key, and everykey:is deleted. v2.9.2src/config.tsreadskeyonly in theelsearm ofif (sharedKey), so on every step that set both it has never had any effect:The
windowsjob's own comment already carried the two live entries proving it —v0-rust-windows-windows-Windows_NT-x64-…before its shared key,v0-rust-windows-Windows_NT-x64-…after — while inferring a composition,{key}-{shared-key or job id}, that those strings contradict. The strings were right; the inference was not. Both are corrected there.Twelve steps across six workflows now carry one spelling each, and the two pairs that must agree —
ci/cache-warm-linux,windows/cache-warm-windows— are identical.One rule asserted with one rule
a_tracked_instruction_may_not_prescribe_the_denied_commit_identityranenforceover the whole committed ruleset — twice, the clean arm being a second evaluation — to assert a single row. 305s on the Windows runner, of a 1482s suite. It is 0.122s narrowed.The shape matters more than the number: a case that evaluates every row to assert one gets slower every time a row is added, and this repository adds rows deliberately.
[[rule]]went 47 → 103 andline_sources2 → 28 between Aug 23 and Sep 1, over which this case went 4s → 305s. The retirement campaign was paying itself a tax on every gate it moved onto the engine.check --rulewas already trusted against these exact bytes —mise.tomlinvokes--rule prose-only,--rule filed-here,--rule memory-graph. What kept this family onenforcewas a comment claiming the read-effect verb refuses the committed config outright. True of an unnarrowedcheck, but--ruleselects the row before the refusal is reached, and only 3 of 103 rows arekind = "command". The comment is corrected in place rather than deleted.Verification
actionlint,zizmor,ci-tools-check,timeout-check,rust-paths-check,commit checkover the range.cli.rs:11426, and passes again when restored. Verified by md5 that the file was unmodified across the run — a first attempt at this check silently did not persist and its "pass" proved nothing.check --rulenaming no declared row exits 1 withno [[rule]] row is declared with id …, never a clean run over nothing.What this PR cannot verify locally
The cache fix is a hosted cache, so the first run here pays one cold build by design and should not be read as a regression. The second run is the measurement. Acceptance is
Compilingback to 0 onciand 1 onwindows, read withgh api …/actions/jobs/{id}/logs—gh run view --logomits build output entirely and reports 0 for a run that compiled 121.One composition detail is unverified from source and confirmed only by the
Cache Keyline the first run prints: thatshared-keycarrying an expression composes as expected. The acceptance above catches it if not.Also in this PR, added after the first push
enforce --rule(crates/batten/src/surface.rs, mirroringCHECK_RULE) forthe_committed_delegating_rule_spawns_nothing_when_its_glob_misses— genuinely about akind = "command"row, socheckmust refuse it by construction. 206s → 0.093s. The flagrefuses an id naming no declared row exactly as
check --ruledoes; that property matters more onthe verb that spawns, not less.
Two follow-on commits the gates asked for:
b6b3180dregenerated the completions and the man pagebut not
it__snapshots__golden_json_schema.snap, which is the third surface and freezes the wholecommand tree as data. Caught by the suite, accepted through
mise run snapshots.refactor(cli)!:Enforce(EnforceFlags).semverrefused the branch — adding a field to theinline
Command::Enforce { json }variant isenum_struct_variant_field_added, and#[non_exhaustive]on the enum governs adding variants, not a variant's fields. There is nocompatible way to add the flag through that enum, so the break is taken either way; it lands in
the shape
CheckFlagsalready uses so the next flag onenforceis additive. The CLI surfacedoes not move — the golden schema is byte-identical across that commit.
Local suite: 3854 of 3854,
verify: fast-forward-green.Not in this PR
The same narrowing for
the_committed_portability_rules_fire_on_every_banned_shape(6 rules) andthe_committed_repo_agnosticism_rules_fire_on_every_banned_shape(4 rules), ~18s each.--ruleissingle-valued, and both cases keep a full-ruleset arm regardless: their clean-tree arms assert that
nothing fires, which is a whole-ruleset property. Narrowing only the dirty arms saves ~9s each and
costs the cross-rule sort-order property the full stdout equality currently proves. Recorded on
CLOUD-1223 rather than half-done.
The gate that was blocking this PR, repaired here
gh pr readyis denied byready-needs-an-answered-reviewunless thereview-answeredfact returns empty. Measured over the last 60 merged PRs: zeroreviews, on any of them. The gate has never once been satisfiable here.
The fact queries GitHub's
reviewsandreviewThreads. The reviewer its ownrefusal names —
@coderabbitai full review— writes neither on this repository'splan; it answers "the author of this PR is on the CodeRabbit Free Plan … please
upgrade", and its automatic pass writes an issue comment. So the predicate read
the wrong object for the reviewer it named, and its
length == 0arm wasunconditionally true.
It was invisible for eight days because every session ran a
battentoo old toenforce it — CLOUD-1326's class, measured here at 0.0.121 against a 0.0.137 tree. A
gate nothing can satisfy and a gate nothing enforces are byte-identical on the
decision surface.
Not a softening. A non-author comment counts only if it carries
auto-generated comment: summarize byAND NOTrate limited by. #819's only suchcomment carries both — a rate-limit stub, a review that did not happen — so the
repaired predicate still returns
["wenzowski"]against this very PR. Shown ableto fail in four directions; the unresolved-thread arm is untouched.
The weakening is groomed on CLOUD-1334 ahead of the change and admitted
(groomed)— both halves of
config-lint's two-source evidence, not the CI trailer-alonefallback.
Closes CLOUD-1334
Closes CLOUD-1223
DO-NOT-CLOSE CLOUD-840 — this PR fixes how the key is composed, which is not that row's remaining
work. CLOUD-840 was reopened on two failing acceptance criteria: total cache size under the 10 GB
cap (measured 225.2 GB) and the job no longer writing bytes nothing restores (203.7 GB across 1,144
PR-ref entries). Neither is touched here.
save-if: falseis its costed remedy and belongs on itsown change.
Refs: CLOUD-840, CLOUD-1223, CLOUD-1225
Generated by Claude Code