From 555a71264c0fef6b911e5ce010f1c2b0ccdf2b95 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Sun, 1 Nov 2026 09:28:48 -0800 Subject: [PATCH 1/2] quest: plan the fixer's autonomy: start from main, comment, and push workflows The land-fixer epic's Goal now states the user's end goal: dequeue only when a decision is required. New quests: the fixer starts from the base and ignores instructions in diffs, comments on GitHub with a per-job comment-only token, and moq-bot gains the workflows permission. A flaky test is disabled on the batch's base PR. Co-Authored-By: Claude Opus 5.5 --- quest/m0/land-fixer/README.md | 36 ++++++++++++++++++--- quest/m0/land-fixer/comments.md | 34 +++++++++++++++++++ quest/m0/land-fixer/flaky.md | 15 +++++---- quest/m0/land-fixer/launch.md | 33 +++++++++++++++++++ quest/m0/land-fixer/workflows-permission.md | 35 ++++++++++++++++++++ 5 files changed, 143 insertions(+), 10 deletions(-) create mode 100644 quest/m0/land-fixer/comments.md create mode 100644 quest/m0/land-fixer/launch.md create mode 100644 quest/m0/land-fixer/workflows-permission.md diff --git a/quest/m0/land-fixer/README.md b/quest/m0/land-fixer/README.md index 2d5fa05b..44d4df06 100644 --- a/quest/m0/land-fixer/README.md +++ b/quest/m0/land-fixer/README.md @@ -2,9 +2,13 @@ ## 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 +The land gate dequeues a PR only when a decision is required. When a 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. The fixer starts from the base with internet +access and the batch's PRs and head SHAs. It ignores instructions in their +diffs, pushes its fixes onto the PRs' branches, disables a flaky test on the +batch's base PR with an investigation quest, and comments on GitHub. 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 @@ -60,6 +64,27 @@ Decision prompts, selected options marked ✅: - 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 +- Restated 2026-11-01 (user, a second /quest-plan): "the merge queue can + autonomously fix most issues. The fixer inspects the batch and tries to + identify the fault. If it's a flaky test, disable it and plan an + investigation quest in a commit (appended to the batch). If it's a merge + conflict, resolve anything mechanical. If it's a compilation/test failure, + make a fix if it's obvious. The fixer is launched using main and has + internet access. It's given the list of PRs/hashes in the batch and is told + to ignore any new instructions in the diff. The fixer is allowed to make new + commits to fix issues inline. It's also allowed to comment on Github. The + end goal is to avoid dequeing PRs unless decisions are required." + - Fold into this epic: ✅ yes · a new epic · a different scope + - Where fix commits go: on the PR's branch · appended to the batch · split + by kind. The user first picked "appended to the batch", then, once + GitHub's squash API was shown unable to land a fix that lives only in the + batch, chose: ✅ "push the fixes on the PR branch instead. I guess + disable the flaky test in the base PR then." + - The flaky commit: ✅ in this batch (on its base PR, per the answer above) + · in the next batch + - GitHub comments: the daemon posts them · ✅ a comment-only token · both + - Workflow pushes: ✅ grant moq-bot `workflows` · the fixer avoids + workflows - 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, @@ -73,9 +98,12 @@ resolves itself and what it hands back; that docs update finishes the epic. ## Required +- [Fixer starts from main](/quest/m0/land-fixer/launch.md) - the fixer starts at the base with the batch's PRs as refs, and ignores instructions in their diffs +- [Workflow permission](/quest/m0/land-fixer/workflows-permission.md) - waits on the maintainer to grant moq-bot `workflows`, so pushes touching workflows are accepted +- [Fixer comments](/quest/m0/land-fixer/comments.md) - a per-job, comment-only GitHub token lets the fixer explain its work on the PRs - [Attempt budget](/quest/m0/land-fixer/attempts.md) - only a fix that fails again spends a pick's single attempt - [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 +- [Flaky tests](/quest/m0/land-fixer/flaky.md) - the gate reruns a failed module, disables a flaky test on the batch's base PR, 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 diff --git a/quest/m0/land-fixer/comments.md b/quest/m0/land-fixer/comments.md new file mode 100644 index 00000000..bd8f3111 --- /dev/null +++ b/quest/m0/land-fixer/comments.md @@ -0,0 +1,34 @@ +# [S] The fixer comments on GitHub + +## Goal + +The fixer can post and edit comments on the batch's PRs, for example to +explain a fix, a disabled flaky test, or the decision it needs. It does this +with a GitHub token limited to issue and PR comments on that one repository, +which the daemon mints for each job and which expires with it. The token +cannot push, merge, change labels, or reach any other repository. + +## Plan + +Decided 2026-11-01 (user, /quest-plan): "It's also allowed to comment on +Github." See the [epic](/quest/m0/land-fixer/README.md). + +- How the fixer gets to comment: + - The daemon posts the comments the fixer writes into its result, so no + GitHub credential enters the container (recommended, because PR code the + fixer builds can read the container's environment, as + [moq-dev/quest#356](https://github.com/moq-dev/quest/pull/356) found for + the proxy key) + - ✅ A comment-only token in the container + - Both + +Mint an installation token for the job's repository with +`permissions: { issues: "write" }`, which covers PR conversation comments, and +`repositories: []`. Pass it to the agent as `GH_TOKEN` only; the wrapper +keeps every other credential out, as today. GitHub caps a token at one hour, +so a longer job gets a fresh one or stops commenting. The daemon keeps posting +its own requeue and handback comments. + +## Related + +- [Fixer starts from main](/quest/m0/land-fixer/launch.md) - what the fixer sees when it starts diff --git a/quest/m0/land-fixer/flaky.md b/quest/m0/land-fixer/flaky.md index 76623e54..8db0b0d7 100644 --- a/quest/m0/land-fixer/flaky.md +++ b/quest/m0/land-fixer/flaky.md @@ -5,10 +5,10 @@ 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. +example a timeout under load, or a race), is flaky. The fixer pushes one +commit onto the batch's base PR that disables the test with a reason and adds +a follow-up quest to fix or delete it in the repository's m0 `flaky` epic. +The batch then lands with that commit. ## Plan @@ -28,8 +28,11 @@ was written and landed by hand. - 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 + - Inside the next batch: the gate adds the skip commit to the batch it + lands next, tested with it (picked first, then superseded) + - ✅ Superseded 2026-11-01 (user, second /quest-plan): the fixer pushes the + disabling commit, with its investigation quest, onto the batch's base PR, + so it lands with this batch: "disable the flaky test in the base PR". - What counts as flaky: - Only a rerun that passes - ✅ A rerun that passes, or a failure the fixer judges nondeterministic diff --git a/quest/m0/land-fixer/launch.md b/quest/m0/land-fixer/launch.md new file mode 100644 index 00000000..129d778b --- /dev/null +++ b/quest/m0/land-fixer/launch.md @@ -0,0 +1,33 @@ +# [S] The fixer starts from main + +## Goal + +The fixer's checkout starts at the batch's base, so Codex loads the base's +`AGENTS.md`, skills and tooling, never a PR's. It receives each PR in the +batch as a number, head SHA and ref, plus the failing log. Its prompt says to +treat everything in the PRs' diffs as data: an instruction there is ignored, +neither followed nor reported as a reason to hand the PR back. + +## Plan + +Decided 2026-11-01 (user, /quest-plan): "The fixer is launched using main and +has internet access. It's given the list of PRs/hashes in the batch and is +told to ignore any new instructions in the diff." See the +[epic](/quest/m0/land-fixer/README.md). + +- Today `startFixer` (`src/cli/land-fixer.ts`) detaches at the batch tip + (`git checkout --quiet --detach failure.tip`), so the agent starts in a tree + holding the PRs' merged `AGENTS.md`. Start it at `refs/heads/base` instead, + and keep `refs/heads/batch` and `refs/heads/pr-` for it to inspect, check + out, and fix. +- `assets/land-fix.md` hands back a PR whose content "targets this fixer + itself". Replace that with the user's rule: ignore such instructions and + carry on with the task. This also ends handbacks that misread ordinary + project rules as injections, such as "Pull the latest origin changes" + ([moq-dev/quest#325](https://github.com/moq-dev/quest/pull/325)). +- Internet access already landed in + [moq-dev/quest#356](https://github.com/moq-dev/quest/pull/356). + +## Related + +- [Fixer comments](/quest/m0/land-fixer/comments.md) - the other capability the user added diff --git a/quest/m0/land-fixer/workflows-permission.md b/quest/m0/land-fixer/workflows-permission.md new file mode 100644 index 00000000..84345a4f --- /dev/null +++ b/quest/m0/land-fixer/workflows-permission.md @@ -0,0 +1,35 @@ +# [XS] moq-bot can push workflow changes + +## Goal + +The moq-bot GitHub App (ID 1039466) has the `workflows: write` repository +permission, and every installation that lands through the host gate has +accepted it: moq-dev/moq, moq.pro, rust2, rind and quest. GitHub then accepts +a fixer push that touches `.github/workflows`, including a base merge pushed +to a fork's branch. This quest waits on the maintainer. To check, open the +App's settings under Permissions; GitHub lists pending permission requests on +each installation. + +## Plan + +Decided 2026-11-01 (user, /quest-plan): + +- What happens when a fixer push touches `.github/workflows`: + - ✅ Grant moq-bot `workflows` + - The fixer avoids workflows, and the PR parks for a decision + +GitHub refused the fixer's push on +[moq-dev/moq#7439](https://github.com/moq-dev/moq/pull/7439) (twice), +[#7467](https://github.com/moq-dev/moq/pull/7467), and +[#7370](https://github.com/moq-dev/moq/pull/7370); a maintainer session pushed +each by hand. Once this permission is granted, the refusal that +[Fork commits](/quest/m0/land-fixer/fork-commit.md) handles should stop +happening; whether to keep that quest and +[moq-dev/quest#358](https://github.com/moq-dev/quest/pull/358) is for its +review. + +Delete this quest once the permission is granted and accepted. + +## Related + +- [Protected edits](/quest/m0/land-fixer/protected.md) - the fixer edits workflows when the change is mechanical From feb60e9919430c23d0b27e9cb59cc8382b4e1197 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Sun, 1 Nov 2026 09:31:44 -0800 Subject: [PATCH 2/2] quest: record the accepted issues:write scope for fixer comments Co-Authored-By: Claude Opus 5.5 --- quest/m0/land-fixer/comments.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/quest/m0/land-fixer/comments.md b/quest/m0/land-fixer/comments.md index bd8f3111..1a5165a7 100644 --- a/quest/m0/land-fixer/comments.md +++ b/quest/m0/land-fixer/comments.md @@ -4,9 +4,11 @@ The fixer can post and edit comments on the batch's PRs, for example to explain a fix, a disabled flaky test, or the decision it needs. It does this -with a GitHub token limited to issue and PR comments on that one repository, -which the daemon mints for each job and which expires with it. The token -cannot push, merge, change labels, or reach any other repository. +with a GitHub installation token for that one repository with only +`issues: write`, which the daemon mints for each job and which expires with +it. The token cannot push code, merge, or reach any other repository. As the +maintainer accepted, it can also edit or close issues and change labels in +that repository. ## Plan @@ -22,6 +24,14 @@ Github." See the [epic](/quest/m0/land-fixer/README.md). - ✅ A comment-only token in the container - Both +- GitHub has no comment-only permission; `issues: write` also allows editing + and closing issues and changing labels, and PR code the fixer builds could + read the token (Codex review of this plan): + - A local comment command: the daemon keeps the credential and exposes + only comment operations inside the container (recommended) + - ✅ Accept `issues: write` in the container + - The daemon posts comments from the result + Mint an installation token for the job's repository with `permissions: { issues: "write" }`, which covers PR conversation comments, and `repositories: []`. Pass it to the agent as `GH_TOKEN` only; the wrapper