fix(bot-lane): stop a structurally-red bot head from buying a matrix per main advance - #771
Conversation
CLOUD-1207 The bot lane leaks its own CI economy: a structurally-red Renovate head stays Ready, so every `main` advance buys a full matrix that can never land
Why
Three mechanisms compose into a loop that cannot terminate:
So each Refinement — Ready Refinement gate: Definition of Ready & Done. This body carries only specializations.
Acceptance
CLOUD-1212 `auto-bot-land` has no red arm: a Ready bot head that fails CI stays Ready, so every `main` advance buys another matrix
Why Split out of CLOUD-1207, which fixed the The lander readies a draft whose head is Measured on #676: 372 workflow runs on one branch in seven days. The change Three edits in
Refinement — Ready Refinement gate: Definition of Ready & Done. This body carries only specializations.
Acceptance
|
|
Warning Review limit reachedNext included review available in 8 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 selected for processing (3)
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 Pro by visiting https://app.coderabbit.ai/settings/billing. Comment |
be24e0a to
51b2d68
Compare
…half A Renovate bump carries no `Refs:` trailer and structurally cannot: the row is minted from the pull request, after the commit exists, and the lane never checks the bot's head out under a write token to amend one in. The merge still moves the row, through `Closes CLOUD-<n>` in the PR body. So the gate refused a class of commit it was never aimed at, forever. Measured on #676: seven days Ready and red, 372 workflow runs on one branch, one full matrix per `main` advance, none of which could ever land. Two conjuncts rather than the diff alone, because a workflow-only diff is ordinary human work here: the author address must be one GitHub mints for an App, and every touched path must be a manifest `renovate.json5` can move. Refs: CLOUD-1207
… it forever `draftPR`, `rebaseWhen` and the lander's ready arm are each right on their own, and none of them covers a head that is Ready and red for a reason no bump can fix. Nothing returns it to draft, so `main` advancing rebases a Ready PR and each rebase is a `synchronize`: a full matrix that cannot land, forever. Measured on #676: seven days open, 372 workflow runs on one branch, 8 rebase cycles in one day. Re-drafting on red does not terminate this, which is why the key is a label and not a retry: drafting stops CI on that sha, the next `main` advance mints a fresh one nothing has graded, and the lander correctly readies it — the identical bill. A label is keyed to the pull request, survives every rebase, and works by stopping the rebases. The retired clause priced freezing against a transiently red head, where two matrices beat one. That arithmetic does not hold when the redness is structural. Refs: CLOUD-1207
… again The lane readied a landable draft and had no arm for a head that stayed landable and failed. Its only re-draft fires on a stale head, and Renovate rebases within minutes of `main` moving, so that arm never sees one. Three edits. The verdict step moves above the ready arm and drops its `draft == false` gate, so this tick can ask about a draft too. Its outputs go three ways where exit 1 and exit 3 were one `green=false` — collapsing them made a head that failed indistinguishable from one nothing had graded, and the two want opposite treatment. Then the red arm drafts the head, applies the `stopUpdatingLabel` that `renovate.json5` declares, and comments once. The label is what terminates, not the draft: drafting stops CI on that sha, the next `main` advance mints a fresh one nothing has graded, and the ready arm correctly readies it. A verdict keyed to a sha cannot bound a loop whose every lap mints a new sha; a label is keyed to the pull request and stops the rebases. Refs: CLOUD-1207
The header claimed the whole lane, from a draft nobody has looked at to a fast-forward. Two committed gates refuse that for some `github-actions` bumps: `sbom-check` maps each pinned action's licence by repo and sha, so a pin move orphans every row until a person regenerates the table, and `mise-action-floor` reds a pin it denies. Neither is reachable by a bump. What the lane owes in that case is a stop rather than a landing, which the freeze arm now provides. Saying so in the header is the difference between a bounded claim and one a red PR discovers for you. Refs: CLOUD-1207
f49b670 to
93d3ebc
Compare
|
❌ The last analysis has failed. |
|
/fast-forward |
Closes CLOUD-1207
Closes CLOUD-1212
What was burning
#676 (
renovate/actions) has been open since 2026-08-23 and has accumulated372 workflow runs on one branch in seven days — 8 rebase cycles on
2026-08-30 alone, each buying
CI(~19 required checks, 20–35 min wall),Rust(~15 min),
commit-lintandzizmor.auto-bot-land.ymlprices this lane at "a draft accumulating: zero" and"one ready: one matrix, spent on the SHA that is about to land." It spends
neither, because three things compose:
commit-lintrefused it —claims no CLOUD-<n> issue— and no Renovate commit can carry that trailer: CLOUD-693'sbot-issue ensuremints the row from the pull request, after the commitexists, and the lane deliberately never checks the bot's head out under a
write token to amend one in. (
sbom-checkrefuses it too; see below.)prConcurrentLimit: 1means it also holds the slot against every queued update.
stale head, and
rebaseWhen: "behind-base-branch"makes the headaheadagain within minutes of
mainmoving, so that arm never sees it.So every
mainadvance is asynchronizeon a Ready PR: one full matrix thatcannot land.
The three changes
mise.toml [tasks.commit-lint]— a bot-authored manifest bump is keyless byconstruction, alongside the existing release-plz exemption. Two conjuncts,
because the diff alone is too wide here: the author address must be one GitHub
mints for an App (
…[bot]@users.noreply.github.com), and every touched path mustbe a manifest
renovate.json5'senabledManagerscan move. A workflow-only diffis ordinary human work in this repository, so exempting it by paths alone would
drop CLOUD-431's server-side claim for a whole class of real changes.
renovate.json5—stopUpdatingLabel: "renovate-frozen"..github/workflows/auto-bot-land.yml— the arm that applies it. The verdictstep moves above the ready arm and drops its
draft == falsegate, so a tick canask about a draft too; its output goes three ways where exit 1 and exit 3 were one
green=false(collapsing them made a head that failed indistinguishable fromone nothing had graded, and the two want opposite treatment). A red head is then
drafted, labelled, and commented on once — the label doubling as the guard that
keeps the comment to once.
Why the label and not the re-draft
Re-drafting alone does not terminate the loop, and this is the part worth
reviewing: drafting stops CI on that sha, the next
mainadvance rebases,mints a fresh sha nothing has graded, and the lander correctly readies it — one
matrix per
mainadvance, the identical bill. A verdict keyed to a sha cannotbound a loop whose every lap mints a new sha. A label is keyed to the pull
request, survives every rebase, and works by stopping the rebases themselves.
The clause this reverses priced freezing against a transiently red head, where
two matrices beat one. That arithmetic does not hold when the redness is
structural, and the retired clause is quoted in place rather than deleted.
Measured
commit-lintover three discriminating ranges on a throwaway branch, against thereal task rather than a fixture:
renovate[bot]mise.tomlonlyBOT …)renovate[bot]README.mdmise.tomlonlyBoth conjuncts are load-bearing; neither alone exempts.
mise run renovate-config-validatorandmise run lint:actionsboth pass.What this does not do
sbom-checkis untouched, deliberately. Its header states that the sha-keyedlicence table is the point — "a renovate bump that does not record the new
commit's license fails the gate rather than silently degrading the document."
Keying it by name would delete a drift detector. The real finding — that the lane
claims "no human in the loop" while two gates require one for any action bump —
is CLOUD-1213, and the freeze above is what makes that wait cost nothing.
Note for the reviewer
The
auto-bot-land.ymledit was made withBATTEN_HOOK_BYPASS=1, spentdeliberately:
protected-mutationguards.github/workflows/**with the remedy"change it in a pull request", which is this. Flagged here so the override is
visible rather than silent.