diff --git a/quest/m0/README.md b/quest/m0/README.md index 9a99ff17..8090a0a0 100644 --- a/quest/m0/README.md +++ b/quest/m0/README.md @@ -40,8 +40,8 @@ ownership-manifest design: ## Required - [Merge through a host gate](/quest/m0/host-land/README.md) - inline merge picks, small batches landed from the maintainer's host, and a Codex fixer instead of bisection +- [The land fixer handles every mechanical failure](/quest/m0/land-fixer/README.md) - attempts, registry network, triage, flaky tests, local mergeability, protected edits, and fork commits - [Landing priority](/quest/m0/land-priority.md) - cheap checks wait a bounded time and the land daemon's check runs ahead of agent builds -- [Local mergeability](/quest/m0/land-mergeability.md) - `quest land` merges with the quest driver instead of ejecting on GitHub's conflict flag - [Accurate head_moved](/quest/m0/land-head-moved.md) - the gate's own base-merge push and batch-mate failures are not reported as a moved head - [Drop emptied Required headings](/quest/m0/required-heading.md) - a clean driver merge that empties a `Required` list passes `quest check` - [GitHub retries](/quest/m0/github-retry.md) - one 5xx or dropped connection no longer fails a command or land step diff --git a/quest/m0/host-land/README.md b/quest/m0/host-land/README.md index 8ac54fa3..5bdcfc1c 100644 --- a/quest/m0/host-land/README.md +++ b/quest/m0/host-land/README.md @@ -7,9 +7,11 @@ inline only when a counted review names its head, or the head is trivial; otherwise `/quest-iterate` runs first. A gate on the maintainer's host lands small batches: a base PR plus up to floor(log₂ n) others that merge cleanly, checked once on the chain's tip and pushed as one squash commit per PR. A -conflict on the base PR or a failing batch ejects the PRs and hands them to an -asynchronous Codex fixer, which makes only mechanical fixes and requeues the -healthy PRs. Bisection goes away. +conflict on the base PR or a failing batch hands the PRs to an asynchronous +Codex fixer, which makes only mechanical fixes and requeues the healthy PRs. +Bisection goes away. Since 2026-11-01 those PRs stay queued and parked while +the fixer works, and only a handback takes one out of the queue; see the +[land fixer epic](/quest/m0/land-fixer/README.md). Non-goals: other repositories adopting the gate (moq-dev/moq, moq-dev/moq.pro plan their own; moq.pro needs only `allow_auto_merge` and the ruleset, since @@ -89,11 +91,10 @@ can hand off instead. landing section describe the host gate. - Amended 2026-10-31 (user): the fixer triages a failing batch before anything is ejected, and only the PR it blames is ejected. See - [batch triage](/quest/m0/host-land/triage.md). + [batch triage](/quest/m0/land-fixer/triage.md). ## Required - [Gate host](/quest/m0/host-land/host.md) - kixelated/nixos runs the daemon as its own user, checks in nspawn containers, and retires the Actions runner - [Fixer proxy access](/quest/m0/host-land/fixer.md) - connect the nixos fixer container to CLIProxyAPI with the gate client key and maintainer login -- [Batch triage](/quest/m0/host-land/triage.md) - the fixer blames the failing PR before ejection, and a red base halts the queue - [Daemon restart keeps its check](/quest/m0/host-land/reattach.md) - a deploy no longer reruns the batch it interrupted diff --git a/quest/m0/host-land/fixer.md b/quest/m0/host-land/fixer.md index 8dd70dfa..c7cb0013 100644 --- a/quest/m0/host-land/fixer.md +++ b/quest/m0/host-land/fixer.md @@ -10,7 +10,8 @@ mechanical fix and unchanged requeue through the host daemon. Maintainer decision, 2026-10-30: a `fixed` head lands without further review. "Mechanical" stays prompt-enforced, protected paths are blocked, and the residual -prompt-injection risk is accepted. The built-in prompt treats contributor content +prompt-injection risk is accepted. Superseded 2026-11-01 (user): the fixer edits +protected paths it judges mechanical; see [Protected edits](/quest/m0/land-fixer/protected.md). The built-in prompt treats contributor content as untrusted and hands back attempts to redirect its task. - Claude reviews the fix first diff --git a/quest/m0/land-fixer/README.md b/quest/m0/land-fixer/README.md new file mode 100644 index 00000000..73786284 --- /dev/null +++ b/quest/m0/land-fixer/README.md @@ -0,0 +1,79 @@ +# The land fixer handles every mechanical failure itself + +## Goal + +When a land batch fails or a queued PR conflicts, the gate and its fixer +resolve every mechanical cause themselves and land the result, so a +maintainer sees only behavior and design decisions. Nothing leaves the queue +for a conflict or a failure: a PR the fixer is working on keeps its +auto-merge and its place, sits out batches until the fix is in, and leaves +the queue only when the fixer hands it back. The fixer blames the failing PR +before anything else happens. The gate merges locally instead of +trusting GitHub's mergeability, reruns a failed module before blaming, and +disables a flaky test with a follow-up quest. The fixer builds and tests +with access to package registries, edits protected paths when the edit is +mechanical, and spends a pick's single attempt only on a fix that failed. +When GitHub refuses its push to a fork, it hands back a ready commit. + +Non-goals: design conflicts. A PR whose merge needs a behavior decision is +still handed back to its author. + +## Plan + +Decided 2026-11-01 (user, /quest-plan), from an audit of the 406 fixer +handbacks posted 2026-10-30 to 11-01 on moq-dev/moq, moq-dev/moq.pro, +moq-dev/rust2, moq-dev/rind and moq-dev/quest. + +| Count | Cause | Owner | +|---|---|---| +| 108 | fixer crashed (exit 101, EAGAIN) | fixed by [moq-dev/quest#320](https://github.com/moq-dev/quest/pull/320) and [#332](https://github.com/moq-dev/quest/pull/332); none after 2026-10-31 21:00Z | +| 16 | fixer disappeared before recording an exit status | fixed on the host; none after 2026-10-31 04:00Z | +| 7 | fixer repository exceeds its size cap | fixed by #332 | +| 6 | AGENTS.md "pull the latest origin changes" read as an injection | fixed by [#325](https://github.com/moq-dev/quest/pull/325) and [#328](https://github.com/moq-dev/quest/pull/328); none after 2026-10-31 18:46Z | +| 98 | "already used its mechanical fix attempt" after a collateral requeue or an earlier conflict fix; each needed a manual re-pick | [Attempt budget](/quest/m0/land-fixer/attempts.md), [Batch triage](/quest/m0/land-fixer/triage.md) | +| ~75 | "requeued unchanged": collateral of a batch another PR broke | [Batch triage](/quest/m0/land-fixer/triage.md) | +| 36 | picked again while fixing; the newer pick is kept, so nothing is lost | not planned | +| 17 | conflicts in protected paths (`flake.nix`, `.github/`), mostly mechanical; resolved by hand on moq-dev/moq#7344, #7315, #7318 | [Protected edits](/quest/m0/land-fixer/protected.md) | +| 17 of the last 40 jobs | `cargo` fails offline ("failed to get `uniffi`"), so Rust fixes were requeued uncompiled: moq-dev/moq#7357 sank three batches, and #6985's lockfile could not be regenerated | [Registry network](/quest/m0/land-fixer/network.md) | +| 2 | GitHub refused the push to a fork because the merge touches workflows: moq-dev/moq#7439, #7467 | [Fork commits](/quest/m0/land-fixer/fork-commit.md) | +| several batches | a flaky ffmpeg timeout ([moq-dev/moq#7480](https://github.com/moq-dev/moq/pull/7480)) ejected whole batches | [Flaky tests](/quest/m0/land-fixer/flaky.md) | +| ~15 | design conflicts, correctly handed back | none | + +Decision prompts, selected options marked ✅: + +- Goal: "the fixer resolves every mechanical failure on its own" + - Yes, as stated (triage and local mergeability stay separate) + - Narrower: only the attempt budget and offline builds + - ✅ Broader: also fold [Batch triage](/quest/m0/land-fixer/triage.md) + and [Local mergeability](/quest/m0/land-fixer/mergeability.md) into + this epic +- How attempts count: see [Attempt budget](/quest/m0/land-fixer/attempts.md). +- How the fixer gets dependencies: see [Registry network](/quest/m0/land-fixer/network.md). +- Protected paths: see [Protected edits](/quest/m0/land-fixer/protected.md). +- Fork pushes GitHub refuses: see [Fork commits](/quest/m0/land-fixer/fork-commit.md). +- Flaky tests: see [Flaky tests](/quest/m0/land-fixer/flaky.md). +- Milestone for the new quests: ✅ m0 · m1 +- The user, starting the work: "The fixer is meant to handle conflicts and + failures without kicking stuff out of the queue." A conflict or a failing + check therefore never ejects a PR; the gate parks it (no auto-merge change, + no ejection comment) while its fixer job runs, and only a handback + disables auto-merge. [Batch triage](/quest/m0/land-fixer/triage.md) and + [Local mergeability](/quest/m0/land-fixer/mergeability.md) follow this + rule. + +The README's landing section and `CONTRIBUTING.md` list what the fixer +resolves itself and what it hands back; that docs update finishes the epic. + +## Required + +- [Attempt budget](/quest/m0/land-fixer/attempts.md) - only a fix that fails again spends a pick's single attempt +- [Registry network](/quest/m0/land-fixer/network.md) - the fixer fetches dependencies, so it builds and tests what it requeues +- [Batch triage](/quest/m0/land-fixer/triage.md) - the fixer blames the failing PR before ejection, and a red base halts the queue +- [Flaky tests](/quest/m0/land-fixer/flaky.md) - the gate reruns a failed module, disables a flaky test in the next batch, and files a follow-up +- [Local mergeability](/quest/m0/land-fixer/mergeability.md) - `quest land` merges with the quest driver instead of ejecting on GitHub's conflict flag +- [Protected edits](/quest/m0/land-fixer/protected.md) - the fixer edits protected paths when it judges the edit mechanical, and the fix lands +- [Fork commits](/quest/m0/land-fixer/fork-commit.md) - a fork push GitHub refuses hands back a commit a maintainer session applies as is + +## Related + +- [Merge through a host gate](/quest/m0/host-land/README.md) - the gate and fixer this epic extends diff --git a/quest/m0/land-fixer/attempts.md b/quest/m0/land-fixer/attempts.md new file mode 100644 index 00000000..c5d7280c --- /dev/null +++ b/quest/m0/land-fixer/attempts.md @@ -0,0 +1,42 @@ +# [S] Only a failed fix spends a pick's attempt + +## Goal + +A maintainer pick's single fixer attempt is spent only when a head the fixer +pushed fails again, or when the fixer hands back. A "requeued unchanged" +verdict, and a fix that only merged the base (cleanly or with resolved +conflicts) leave the attempt unspent. A PR that conflicts again as the base +moves is then fixed again without a re-pick. + +## Plan + +Decided 2026-11-01 (user, /quest-plan of the land fixer audit; see the +[epic](/quest/m0/land-fixer/README.md)). In 2026-10-30 to 11-01, 98 handbacks +said only "This maintainer pick already used its mechanical fix attempt", and +each needed a manual re-pick. Most had spent the attempt on a requeue-unchanged +verdict as collateral of another PR's failure, or on an earlier conflict fix +before the base moved again. + +- How attempts count: + - ✅ Only failed fixes count: requeue-unchanged and base-merge fixes leave + the attempt unspent; it is spent when a pushed fix fails again, or on a + handback + - One attempt per head + - N attempts per pick (for example 3) + - Keep one per pick + +The attempt record is `fix-attempt-.json` (`src/cli/land-fixer.ts`): its +`pick`, `requeued`, and `head` fields, read by `handbackRepeated`. Add an +`outcome` field (`unchanged`, `merged`, or `fixed`) and have +`handbackRepeated` count only `fixed`. A record written before this field +existed has no `outcome` and counts as spent, as today, so a deploy never +hands a pick a second attempt it already used. A head the fixer changed beyond a base merge that then fails its +checks still hands back, so a bad fix cannot loop. + +[Batch triage](/quest/m0/land-fixer/triage.md) keeps a collateral ejection +from reaching the fixer at all; this quest owns the counting rule for every +outcome, including the ones triage still produces. + +## Related + +- [Batch triage](/quest/m0/land-fixer/triage.md) - blames one PR, so collateral PRs are not ejected diff --git a/quest/m0/land-fixer/flaky.md b/quest/m0/land-fixer/flaky.md new file mode 100644 index 00000000..76623e54 --- /dev/null +++ b/quest/m0/land-fixer/flaky.md @@ -0,0 +1,61 @@ +# [M] The gate reruns a failure, then disables a flaky test + +## Goal + +When a batch's check fails, `quest land` reruns the failing check modules +once on the same tip before blaming or ejecting anything. A test that failed +and then passed, or whose failure the fixer judges nondeterministic (for +example a timeout under load, or a race), is flaky. The gate lands the batch, +adds a commit to the next batch that disables the test with a reason, and +files a follow-up quest to fix or delete it in the repository's m0 `flaky` +epic. + +## Plan + +Decided 2026-11-01 (user, /quest-plan of the land fixer audit; see the +[epic](/quest/m0/land-fixer/README.md)). A flaky ffmpeg render timeout in +moq's interop tests ejected whole batches on 2026-11-01. The fixer then +recognized it as flaky, but only after the batch's PRs had been ejected and +their attempts spent. The fix ([moq-dev/moq#7480](https://github.com/moq-dev/moq/pull/7480)) +was written and landed by hand. + +- What the gate does with a flaky failure: + - ✅ Rerun the failed module once + - The fixer names the flake + - Nothing new +- User follow-up: "make a rule to immediately disable any flakey tests and + create a follow-up to fix/delete them". +- Who disables: + - The fixer opens and queues its own PR + - The fixer opens a PR, and a maintainer picks it + - ✅ Inside the next batch: the gate adds the skip commit to the batch it + lands next, tested with it +- What counts as flaky: + - Only a rerun that passes + - ✅ A rerun that passes, or a failure the fixer judges nondeterministic +- Where the follow-up goes: + - ✅ A standing `flaky` epic in the repository's m0, one quest per + disabled test + - The earliest milestone, ungrouped + - m1 + +How the gate reruns one module is up to the repository. `quest land` passes +the failed module names to the check command (for example through +`QUEST_LAND_RERUN`). A check command that ignores them reruns whole. moq's +`sh/dispatch.sh` already reports `ci: failed`. + +The fixer writes the skip in the test framework's own form, with the reason +naming the follow-up quest: `#[ignore = "flaky: /quest/m0/flaky/.md"]`, +`test.skip`, `pytest.mark.skip`, and so on. A disabled test that is a quest's +acceptance test still counts as its quest's work. The follow-up quest's Goal +says to fix the test or delete it, and links the batch logs that showed the +flake. + +Flaky verdicts never spend a PR's attempt +([Attempt budget](/quest/m0/land-fixer/attempts.md)), and a batch whose only +failure was flaky lands without ejecting anyone +([Batch triage](/quest/m0/land-fixer/triage.md)). + +## Related + +- [Batch triage](/quest/m0/land-fixer/triage.md) - blame runs after the rerun diff --git a/quest/m0/land-fixer/fork-commit.md b/quest/m0/land-fixer/fork-commit.md new file mode 100644 index 00000000..accf2d79 --- /dev/null +++ b/quest/m0/land-fixer/fork-commit.md @@ -0,0 +1,38 @@ +# [S] A refused fork push hands back a ready commit + +## Goal + +When GitHub refuses the fixer's push to a fork because the commit touches +`.github/workflows` (the App has no `workflows` permission), the handback +carries the fixer's validated resolution. A maintainer session applies and +pushes it without redoing the merge. + +## Plan + +Decided 2026-11-01 (user, /quest-plan of the land fixer audit; see the +[epic](/quest/m0/land-fixer/README.md)). On +[moq-dev/moq#7439](https://github.com/moq-dev/moq/pull/7439) and +[#7467](https://github.com/moq-dev/moq/pull/7467) the fixer's merge of the +base brought in the base's workflow changes, and GitHub refused the push. A +maintainer session recovered #7439's commit from the daemon's +`repository.git` through a temporary ref and a bundle over SSH. + +- What happens on a refused fork push: + - Grant moq-bot the `workflows` permission + - ✅ Hand back a ready commit + - Leave it manual + +In any repository where the App lacks the `workflows` permission, GitHub +refuses the App's push of such a commit, so the App cannot publish it on a +fork or an upstream ref. The commit itself can still travel through Git, as +the bundle recovery showed. The handback comment records the PR head SHA, +the base SHA, the expected tree hash, and the fixer's resolution as a patch +against the merge's conflicted result. A command such as `quest land apply +` stops if the PR's head is no longer the recorded one, then merges that +base, applies the patch, checks the tree hash, and leaves a commit for the +maintainer to push. Size the comment to GitHub's limit and +hand back plainly when the patch does not fit. + +## Related + +- [Protected edits](/quest/m0/land-fixer/protected.md) - the fixer's own workflow edits diff --git a/quest/m0/land-mergeability.md b/quest/m0/land-fixer/mergeability.md similarity index 73% rename from quest/m0/land-mergeability.md rename to quest/m0/land-fixer/mergeability.md index acaa9d56..8eab93cc 100644 --- a/quest/m0/land-mergeability.md +++ b/quest/m0/land-fixer/mergeability.md @@ -1,12 +1,14 @@ -# [XS] quest land merges locally instead of trusting GitHub's mergeability +# [S] quest land merges locally instead of trusting GitHub's mergeability ## Goal `quest land` never ejects a PR as `merge_conflict` because of GitHub's mergeability alone. When the squash API refuses a PR that GitHub marks as conflicting, the gate merges the base into it locally with the quest merge -driver, pushes that merge, and lands the PR. It ejects only when the local -merge also conflicts. +driver, pushes that merge, and lands the PR. When the local merge also +conflicts, the PR stays queued and parked while the fixer resolves it; it +leaves the queue only on a handback (2026-11-01, see the +[epic](/quest/m0/land-fixer/README.md)). ## Plan diff --git a/quest/m0/land-fixer/network.md b/quest/m0/land-fixer/network.md new file mode 100644 index 00000000..f1dda70c --- /dev/null +++ b/quest/m0/land-fixer/network.md @@ -0,0 +1,46 @@ +# [S] The fixer reaches package registries + +## Goal + +The fixer container can fetch dependencies from the package registries a +repository's lockfiles name, so `cargo`, `bun`, `uv`, and `go` build and test +a fix before the fixer reports `fixed` or `requeue`. It does not use GitHub's +API or push, and the only credential in the container stays the gate's +CLIProxyAPI client key ([Fixer proxy access](/quest/m0/host-land/fixer.md)). + +## Plan + +Decided 2026-11-01 (user, /quest-plan of the land fixer audit; see the +[epic](/quest/m0/land-fixer/README.md)). In 17 of moq's last 40 fix jobs, +`cargo` failed offline with "failed to get `uniffi` as a dependency", a git +dependency the container cannot fetch. The fixer requeued Rust fixes it never +compiled ([moq-dev/moq#7357](https://github.com/moq-dev/moq/pull/7357) sank +three batches), and could not regenerate +[moq-dev/moq#6985](https://github.com/moq-dev/moq/pull/6985)'s lockfile. + +- How the fixer gets dependencies: + - Host prefetches the job's lockfile dependencies and mounts them read-only + - ✅ Allow registry network + - Leave it + +Found while starting the work (2026-11-01): the host is not what blocks it. +kixelated/nixos `modules/ci-egress.nix` gives the fixer container +(`qland-fix`) the same egress as the check containers, the public internet +without private ranges, and the fixer runs Codex with +`--dangerously-bypass-approvals-and-sandbox`. The fixer chooses `--offline` +itself because `assets/land-fix.md` says "Never push or contact GitHub", and +a git dependency's fetch from `github.com` is contact. So the change is the +prompt: fetching dependencies from package registries and from +`github.com`/`codeload.github.com` for git dependencies is allowed, while +GitHub's API, pushing, and any other contact stay forbidden. +[Fixer proxy access](/quest/m0/host-land/fixer.md)'s "network access only to +CLIProxyAPI" describes the intent, not the host as built; correct it in the +same PR. + +`assets/land-fix.md` then tells the fixer to build and test the packages its +fix touches, and to report what it ran in the result's reason. A fix it could +not verify hands back with the command's output. + +## Related + +- [Fixer proxy access](/quest/m0/host-land/fixer.md) - the container's existing network rules diff --git a/quest/m0/land-fixer/protected.md b/quest/m0/land-fixer/protected.md new file mode 100644 index 00000000..6107cfe6 --- /dev/null +++ b/quest/m0/land-fixer/protected.md @@ -0,0 +1,44 @@ +# [S] The fixer edits protected paths when the edit is mechanical + +## Goal + +The fixer may resolve conflicts and failures in protected paths (`.github/`, +`flake.nix`, `nix/`, justfiles, and the rest of the protected set) when it +judges the edit mechanical. A head with such an edit lands like any other +fix. + +## Plan + +Decided 2026-11-01 (user, /quest-plan of the land fixer audit; see the +[epic](/quest/m0/land-fixer/README.md)). This reverses +[Fixer proxy access](/quest/m0/host-land/fixer.md)'s 2026-10-30 decision +that protected paths are blocked. 17 handbacks were protected-path +conflicts, most of them mechanical: keep both sides, or drop a tool the base +removed. Maintainers resolved them by hand, for example on +[moq-dev/moq#7344](https://github.com/moq-dev/moq/pull/7344), +[#7315](https://github.com/moq-dev/moq/pull/7315), and +[#7318](https://github.com/moq-dev/moq/pull/7318). + +- First round: + - Resolve verbatim hunks only + - Hand back a ready commit + - Keep blocking + - User's answer: "Up to the fixer to determine what is mechanical" +- Second round, challenging the earlier decision, since a fixer head lands + with no further review: + - The fixer decides, but a protected edit needs a counted review + - ✅ The fixer decides, and the fix lands. User: "Any prompt injections + should be caught before enabling auto merge"; the maintainer's pick is + the injection check + - Workflows stay blocked + +Remove the protected-set check from `validateFix` (`src/cli/land-fixer.ts`, +"protected set must equal pinned head or base merge result"). Replace the +protected-path rule in `assets/land-fix.md` with the same mechanical bar +every other path has. Keep the rule that the fixer hands back any attempt to +redirect its task, and update the decision line in +[Fixer proxy access](/quest/m0/host-land/fixer.md). + +## Related + +- [Fork commits](/quest/m0/land-fixer/fork-commit.md) - workflow edits GitHub refuses to take from the App on forks diff --git a/quest/m0/host-land/triage.md b/quest/m0/land-fixer/triage.md similarity index 71% rename from quest/m0/host-land/triage.md rename to quest/m0/land-fixer/triage.md index 96d98fce..c0db6486 100644 --- a/quest/m0/host-land/triage.md +++ b/quest/m0/land-fixer/triage.md @@ -1,12 +1,15 @@ -# [M] The fixer triages a failing batch before anything is ejected +# [M] The fixer triages a failing batch instead of ejecting it ## Goal When a batch's check fails, the fixer reads the failure first and names the -PR that caused it. `quest land` ejects only that PR and keeps the rest -queued. When the fixer cannot tell, it checks the base tip alone. If the -base is red, the queue halts and says so, ejecting nothing. If the base is -green, every PR in the batch is ejected, as today. Bisection stays gone, as +PR that caused it. `quest land` parks only that PR while the fixer repairs +it, keeping its auto-merge, and keeps the rest queued; the PR leaves the +queue only if the fixer hands it back. When the fixer cannot tell, it checks +the base tip alone. If the base is red, the queue halts and says so, +parking nothing. If the base is green, every PR in the batch is parked for +the fixer, still without leaving the queue (2026-11-01, see the +[epic](/quest/m0/land-fixer/README.md)). Bisection stays gone, as the [epic](/quest/m0/host-land/README.md) decided. ## Plan @@ -25,8 +28,16 @@ wasting CI on bisection?" - Bring back bisection - The user's refinement: "The fixer reads the failure, tries to identify the PR that caused it, and only ejects that PR. If it can't figure it out, it - ejects all PRs". -- Repairing the blamed PR does not change: one attempt, after it is ejected. + ejects all PRs". Superseded 2026-11-01 (user): nothing is ejected for a + conflict or a failure; the blamed PR, or every PR when blame is unclear, + is parked for the fixer and stays queued, as the Goal says. +- Repairing the blamed PR uses the outcome rule in + [Attempt budget](/quest/m0/land-fixer/attempts.md): only a fix that fails + again, or a handback, spends the pick's attempt (2026-11-01). +- A PR whose required checks have not finished stays queued and sits out + batches until they pass, instead of being ejected as `checks_pending` + (2026-11-01: [moq-dev/quest#354](https://github.com/moq-dev/quest/pull/354) + was ejected that way while its CI ran). - Blame is unclear, so check the base tip alone. A red base halts the queue with one visible notice instead of ejecting PRs that did nothing wrong ([moq-dev/moq#7196](https://github.com/moq-dev/moq/pull/7196), @@ -56,7 +67,8 @@ wasting CI on bisection?" [#7306](https://github.com/moq-dev/moq/pull/7306), [#7284](https://github.com/moq-dev/moq/pull/7284) and [#7312](https://github.com/moq-dev/moq/pull/7312). Only the blamed PR's - attempt is spent. + attempt is spent. [Attempt budget](/quest/m0/land-fixer/attempts.md) + owns the counting rule for every outcome (2026-11-01). - Collect a finished fixer's result without waiting for the running batch (user, 2026-10-31). On 2026-10-31 moq's fixer finished at 14:55, having fixed [moq-dev/moq#7383](https://github.com/moq-dev/moq/pull/7383) and