diff --git a/quest/m0/README.md b/quest/m0/README.md index 2dfcb209..bf047680 100644 --- a/quest/m0/README.md +++ b/quest/m0/README.md @@ -85,6 +85,9 @@ ownership-manifest design: - [Land through the merge queue with an async fixer](/quest/m0/queue-watcher/README.md) - GitHub's merge queue lands one PR at a time, and a host watcher fixes and re-enqueues each PR it drops - [Merge through a host gate](/quest/m0/host-land/README.md) - the host's fixer containers, and the frozen batched gate for the repositories not yet switched - [The land fixer resolves every mechanical failure](/quest/m0/land-fixer/README.md) - workflow pushes and the PR's own CI as context +- [Reviewer usage limits](/quest/m0/review-quota.md) - `quest review` exits distinctly on a reviewer's quota and prints the reset time +- [Drafts in quest waiting](/quest/m0/waiting-drafts.md) - `quest waiting` lists open draft PRs' `## Waiting` items beside the tree's +- [Rejected options in decision records](/quest/m0/decision-records.md) - grill and plan label rejected options so no one reads them as qualifiers - [Kix decision UI](/quest/m0/kix/README.md) - planning and implementation through prompts and summaries, run on the maintainer's own runners - [Codex cloud off](/quest/m0/codex-cloud-off.md) - automatic Codex cloud reviews stop once no open PR relies on them - [Bun EFAULT report](/quest/m0/bun-efault.md) - report concurrent `rmSync` `EFAULT` on Windows, and drop the workaround once Bun fixes it @@ -93,4 +96,6 @@ ownership-manifest design: - [Agent workflow helpers](/quest/m0/helpers/README.md) - CLI commands for CI waits, PR status, claims, quest moves, and serialized merges - [Worktree hooks here](/quest/m0/worktree-hooks.md) - `quest` reaches Claude Code's hooks and the merge driver in this repository, then `quest init` installs the hooks - [Remote control worktrees](/quest/m0/worktree-routes.md) - live-check `claude remote-control --spawn worktree` against the hooks, from a session spawned on claude.ai +- [Session checkouts released](/quest/m0/session-checkouts.md) - a Claude session's own checkout is released when the session ends and reclaimed on resume +- [claude rm upstream report](/quest/m0/claude-rm-upstream.md) - waiting on approval: ask Claude Code to run `WorktreeRemove` instead of advising an unlock - [opencode 2.x](/quest/m0/opencode-sdk.md) - the runner drives the opencode 2.x binary through `@opencode/client`, removing the interim 1.19.0 pin diff --git a/quest/m0/claude-rm-upstream.md b/quest/m0/claude-rm-upstream.md new file mode 100644 index 00000000..49d6ee26 --- /dev/null +++ b/quest/m0/claude-rm-upstream.md @@ -0,0 +1,34 @@ +# [XS] Report claude rm's unlock advice upstream + +## Goal + +anthropics/claude-code has an issue asking `claude rm` to dispatch the +`WorktreeRemove` hook for a checkout a `WorktreeCreate` hook made, as remote +control already does, instead of telling the user to run +`git worktree unlock`, which breaks the hook's lock. Delete this quest once +the issue is filed, or abandon it if the maintainer declines. + +## Plan + +Decided 2026-11-02 (coordinator, /quest-spawn follow-ups, after the user +asked the coordinator to decide follow-ups from evidence): plan it, [XS], +m0, waiting on the maintainer's approval to post. + +- [moq-dev/quest#425](https://github.com/moq-dev/quest/pull/425)'s probe + with Claude Code 2.1.316: `claude rm` on an agent-view session's + hook-made checkout reports it "locked … by something other than Claude + Code" and suggests `git worktree unlock`. The README now tells users to + run `quest worktree remove` first. +- No upstream issue covers it (searched 2026-11-02). Related but different: + [anthropics/claude-code#79872](https://github.com/anthropics/claude-code/issues/79872) + (`claude agents` delete skips plugin `WorktreeRemove` hooks), + [#93676](https://github.com/anthropics/claude-code/issues/93676), + [#101283](https://github.com/anthropics/claude-code/issues/101283), and + [#99270](https://github.com/anthropics/claude-code/issues/99270). Link + them in the report. +- Include the Claude Code version, the minimal reproduction from #425's + scratch repository, and the expected behaviour. + +## Waiting + +- Maintainer: approve posting on anthropics/claude-code, which is outside the kixelated and moq-dev orgs where agents may post without asking; check by a ✅ recorded in this quest's Plan diff --git a/quest/m0/decision-records.md b/quest/m0/decision-records.md new file mode 100644 index 00000000..95cd859a --- /dev/null +++ b/quest/m0/decision-records.md @@ -0,0 +1,20 @@ +# [XS] Recorded decisions mark the rejected options + +## Goal + +The grill and plan skills record each decision with the chosen option +marked and the rejected options labelled as rejected, such as +`✅ Yes, as stated (rejected: without the overlap check; drop it)`, so no +reader takes a rejected option for a qualifier of the chosen one. + +## Plan + +Decided 2026-11-02 (user, /quest-spawn follow-ups): ✅ plan as stated +(rejected: drop it); ✅ m0 (rejected: m1, m2). + +- Raised by [moq-dev/quest#416](https://github.com/moq-dev/quest/pull/416): + Codex read "✅ Yes, as stated; without the overlap check; drop it" as + dropping the overlap check, and the agent spent a review round refuting + it. +- Change the recording instruction in `assets/skills/grill.md` and + `assets/skills/plan.md`. Existing records stay as written. diff --git a/quest/m0/kix/sandbox/README.md b/quest/m0/kix/sandbox/README.md index 2e0021dd..3270a659 100644 --- a/quest/m0/kix/sandbox/README.md +++ b/quest/m0/kix/sandbox/README.md @@ -77,4 +77,7 @@ Shipped in [moq-dev/quest#212](https://github.com/moq-dev/quest/pull/212) ## Required +- [No Unix sockets in opencode jobs](/quest/m0/kix/sandbox/seccomp.md) - srt's seccomp helper is packaged, so commands can't reach the Nix daemon, and the runner refuses to start without it - [Boundary test in CI](/quest/m0/kix/sandbox/live-test.md) - the opencode sandbox's hostile-repository test runs on Linux CI with a scripted model +- [Trust rechecked on queue](/quest/m0/kix/sandbox/trust-on-queue.md) - a job's head trust is reread whenever it queues, so the approval decision shows without a free runner +- [Claude Code boundary test](/quest/m0/kix/sandbox/claude-boundary.md) - the same hostile-repository test for Claude Code jobs, with a fake model set in the account's settings diff --git a/quest/m0/kix/sandbox/claude-boundary.md b/quest/m0/kix/sandbox/claude-boundary.md new file mode 100644 index 00000000..6032b882 --- /dev/null +++ b/quest/m0/kix/sandbox/claude-boundary.md @@ -0,0 +1,55 @@ +# [M] The Claude Code sandbox's boundary test runs in CI + +## Goal + +Claude Code jobs get the same checked-in hostile-repository boundary test +that opencode jobs got in +[moq-dev/quest#404](https://github.com/moq-dev/quest/pull/404): a scripted +fake model, set as the account's endpoint in its home, drives the pinned +Claude Code through the runner's launch path on Linux CI, and every escape +attempt is refused. +A Claude Code bump that weakens the boundary fails CI before it lands. + +## Plan + +Decided 2026-11-02 (coordinator, /quest-spawn follow-ups, after the user +asked the coordinator to decide follow-ups from evidence): plan it, [M], +in this epic after the seccomp fix. + +Evidence, gathered 2026-11-02: + +- The only Claude Code coverage is unit tests of its settings and + environment (`test/runner.test.ts`). The boundary was last checked by hand + ("verified live on Linux with Claude Code 2.1.291 and 2.1.305"), and + [moq-dev/quest#242](https://github.com/moq-dev/quest/pull/242) bumped the + pin without a rerun although 2.1.305 added `allowNestedUserNamespaces`. +- The opencode test found a real hole (the missing seccomp helper, see + [Unix sockets](/quest/m0/kix/sandbox/seccomp.md)), so these tests pay off. + +Work: + +- Reuse #404's attempts and layout: denied reads by path and symlink, + writes outside the checkout and job temp directory, planted project + settings and hooks, the network directly and through the proxy, and the + Unix-socket probe from the seccomp quest. +- Assert that `TMPDIR` inside a command is the job's private directory and + is writable. Reading Claude Code 2.1.305 says its strict policy exports + `TMPDIR=/claude-`, which the + runner's `TMPDIR` (`src/runner/claude.ts`) puts inside `allowWrite`; the + test confirms it instead of trusting the read. +- Point the account at the fake model in its home, as #404's `signIn` + configures opencode's provider: `ANTHROPIC_BASE_URL` in the `env` of + `$CLAUDE_CONFIG_DIR/settings.json`, which `--setting-sources user` loads. + An ambient `ANTHROPIC_BASE_URL` never reaches Claude Code, since + `jobEnvironment` (`src/runner/claude.ts`) keeps only `keptEnvironment`'s + allowlist (`src/runner/sandbox.ts`); leave that allowlist as it is. If + settings' `env` also reaches the commands, the fake key there is no + secret, so the unreadable-secret attempt reads a planted credential file + in the home instead. +- Like #404, each shell attempt first succeeds outside the sandbox, and the + test fails rather than skips on Linux when the sandbox can't start. + +## Required + +- [No Unix sockets in opencode jobs](/quest/m0/kix/sandbox/seccomp.md) - the Unix-socket probe this test reuses +- [Boundary test in CI](/quest/m0/kix/sandbox/live-test.md) - #404's attempts and layout this test reuses diff --git a/quest/m0/kix/sandbox/seccomp.md b/quest/m0/kix/sandbox/seccomp.md new file mode 100644 index 00000000..65255601 --- /dev/null +++ b/quest/m0/kix/sandbox/seccomp.md @@ -0,0 +1,56 @@ +# [S] opencode jobs' commands can't reach Unix sockets + +## Goal + +The sandbox-runtime (srt) that runs opencode jobs' commands applies its +seccomp filter, so a sandboxed command cannot create a Unix socket and so +cannot connect to one, the Nix daemon's included. The runner refuses to start an opencode job when +srt reports Unix-socket blocking disabled, and the boundary test probes the +daemon socket, so a package bump that drops the filter fails CI. srt's +always-writable `/tmp/claude` is denied too. + +## Plan + +Decided 2026-11-02 (coordinator, /quest-spawn follow-ups, after the user +asked the coordinator to decide follow-ups from evidence): plan it, [S], +first in this epic, since it is a hole in a boundary the README says holds. + +Evidence, gathered 2026-11-02: + +- nixpkgs' `sandbox-runtime-0.0.81`, which `flake.nix` uses as is, ships + `vendor/seccomp/build.ts` but no compiled `apply-seccomp` helper. srt then + logs `apply-seccomp binary not available - unix socket blocking disabled` + (seen in [moq-dev/quest#435](https://github.com/moq-dev/quest/pull/435)'s + session on 2026-11-03T03:55Z). +- Run with the runner's `runtimeSettings` shape (reads of `/home` and `/tmp` + denied), `nix store ping --store daemon` inside srt answered + `Version: 2.35.2`. `nix config show trusted-users` is + `root kixelated github-runner`, so a job's command on this host could pass + the daemon root-level options. This contradicts the epic's "Commands have + no Nix daemon". No runner was deployed on the host yet. +- [moq-dev/quest#404](https://github.com/moq-dev/quest/pull/404)'s boundary + test passed because it probes no Unix socket. +- srt 0.0.81 lists `/tmp/claude` in `SANDBOX_OWN_WRITE_PATHS`, which it never + drops (`dist/sandbox/sandbox-utils.js`). The directory doesn't exist on + this host and a sandboxed `mkdir` lands on srt's private tmpfs, so it + matters only if something on the host creates it. Deny it anyway while + editing the same settings. + +Work: + +- Build `apply-seccomp` for the flake's `sandbox-runtime` (an override that + compiles it per `build.ts` against static libseccomp), or package the npm + tarball's prebuilt helper and pass `seccomp.applyPath`. Prefer the build + from source, matching how the flake pins the rest. +- The runner refuses an opencode account when srt reports Unix-socket + blocking disabled, with an error that names the missing helper. +- Add to [#404](https://github.com/moq-dev/quest/pull/404)'s boundary test, + in that PR if it is still open: connecting to the Nix daemon socket, and to a + socket the test opens outside the sandbox, are both refused. +- The filter blocks creating a Unix socket, not using one a command + inherits or receives over `SCM_RIGHTS`. Have the boundary test list the + command's open descriptors (`/proc/self/fd`) and fail on any Unix socket + other than its stdio's, so nothing the runner or opencode passes down + reaches a host socket. +- Add `/tmp/claude` to `denyWrite` if srt honours it over its own write + paths without leaving a placeholder behind; otherwise record why not. diff --git a/quest/m0/kix/sandbox/trust-on-queue.md b/quest/m0/kix/sandbox/trust-on-queue.md new file mode 100644 index 00000000..2f12520b --- /dev/null +++ b/quest/m0/kix/sandbox/trust-on-queue.md @@ -0,0 +1,23 @@ +# [S] A job's head trust is rechecked when it queues + +## Goal + +kix rechecks a PR job's head trust whenever the job becomes queued, after an +answer or when a runner's lease expires, not only when a runner has a free +slot. A job whose head is no longer trusted shows the "Head to approve" +decision at once instead of sitting as "Queued" until a slot frees. + +## Plan + +Decided 2026-11-02 (user, /quest-spawn follow-ups): ✅ plan as stated +(rejected: drop it); ✅ m0 (rejected: m1, m2). + +- Raised by [moq-dev/quest#402](https://github.com/moq-dev/quest/pull/402), + which added per-head approval and rechecks trust at job creation and at + assignment. Existing behaviour: a queued job's trust is reread only when a + runner slot frees, so a job resumed after kix pushed new commits looks + idle while it needs a maintainer. +- Reuse #402's trust classification; call it from every transition into + `queued`. Keep the recheck at assignment as the authority. +- Test a resume after kix pushed, and a lease expiry, each surfacing the + decision without a free runner slot. diff --git a/quest/m0/review-quota.md b/quest/m0/review-quota.md new file mode 100644 index 00000000..804d95a8 --- /dev/null +++ b/quest/m0/review-quota.md @@ -0,0 +1,31 @@ +# [S] quest review reports a reviewer's usage limit + +## Goal + +When the reviewer CLI fails because its account hit a usage limit or quota, +`quest review` exits with a distinct status and prints the reset time, so an +agent records the outage as a `## Waiting` item without parsing the +reviewer's error text. + +## Plan + +Decided 2026-11-02 (user, /quest-spawn follow-ups): ✅ plan as stated +(rejected: drop it); ✅ m0 (rejected: m1, m2). + +- Raised by [moq-dev/quest#411](https://github.com/moq-dev/quest/pull/411), + whose guide rule turns a reviewer's quota or login failure into a + `## Waiting` item naming when it resets. About 29 sessions between + 2026-10-30 and 11-01 ended on a reviewer's weekly quota. +- Reuse the matchers that exist: Claude Code's usage-limit `LIMIT` in + `src/runner/claude.ts` and `quest agent`'s `EXHAUSTED` in + `src/cli/agent.ts`. Move them where both and `quest review` import them, + rather than adding a third copy. +- The contract: `quest review` exits 3, which it uses for nothing else + (0 is a round, 1 a failure, 2 a usage error), and prints one stderr line, + `error: hit its usage limit; resets