diff --git a/quest/m0/README.md b/quest/m0/README.md index 84053cb0..aeee766b 100644 --- a/quest/m0/README.md +++ b/quest/m0/README.md @@ -104,8 +104,7 @@ ownership-manifest design: - [Gate bench](/quest/m0/gate-bench.md) - measure headroom admission on an idle host - [Gate load wording](/quest/m0/gate-load-wording.md) - stop naming Spotlight as the cause of a fresh macOS runner's load - [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 -- [Wrapped worktree hooks](/quest/m0/wrapped-hooks.md) - `quest init` accepts a hook that runs `… worktree hook` through a wrapper such as `nix run` +- [Agents get the dev shell from user settings](/quest/m0/user-dev-shell/README.md) - maintainers' own Claude and Codex settings load the dev shell; repositories drop dev-shell hooks, Quest worktree hooks, and `nix develop` instructions, and `quest init` writes no 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 diff --git a/quest/m0/session-checkouts.md b/quest/m0/session-checkouts.md index 01d06a83..3c9cbce8 100644 --- a/quest/m0/session-checkouts.md +++ b/quest/m0/session-checkouts.md @@ -23,12 +23,24 @@ Evidence, gathered 2026-11-02: Work: -- Install `SessionEnd` and `SessionStart` hook entries through - `quest init`, recognised by `init` and `uninstall` like the others. +- Add `SessionEnd` and `SessionStart` entries to the opt-in user-level hook + setup the docs describe. Amended 2026-11-03 (maintainer, /quest-unblock): + `quest init` writes no hook entries anymore + ([quest init writes no hooks](/quest/m0/user-dev-shell/init-hooks.md)), + so this quest no longer installs them through `init`. - `SessionEnd` releases (never removes) the session's checkout; `SessionStart` claims it again on resume. `/clear` also fires `SessionEnd`, so release is idempotent and the next `SessionStart` reclaims it. An idle background session never ends; leave it held. +- Remote control's `bridge-*` checkouts are session checkouts too, and the + release covers them if `SessionEnd` fires in a bridge-spawned session. + [Remote control's live check](/quest/m0/worktree-routes.md) confirms + whether it does. Release only prunes the declared scratch: it keeps every + other file and Git's worktree lock (`release` and `prune` in + `src/cli/worktree.ts`; only `remove` unlocks). The bridge still decides at + session end, as that quest describes: it removes a clean checkout through + `WorktreeRemove` and keeps one with changes. Verify that order in the same + live check. - Claude Code bounds `SessionEnd` hooks by a short budget (`CLAUDE_CODE_SESSIONEND_HOOKS_TIMEOUT_MS`) and does not wait past it. Measure a release with scratch to delete, and set the entry's timeout to diff --git a/quest/m0/user-dev-shell/README.md b/quest/m0/user-dev-shell/README.md new file mode 100644 index 00000000..441ea38c --- /dev/null +++ b/quest/m0/user-dev-shell/README.md @@ -0,0 +1,77 @@ +# Agents get the dev shell from user settings, not repository config + +## Goal + +Agents in moq-dev's repositories run tools without caring about Nix: each +maintainer's own Claude Code and Codex settings load the repository's dev +shell into every session's commands, so no repository forces direnv, Nix, or +`quest` on its contributors through checked-in hooks. No repository carries a +dev-shell `SessionStart` hook, a Quest worktree hook, or AGENTS.md +instructions to wrap commands in `nix develop`, and `quest init` writes no +hook entries. + +## Plan + +Decided 2026-11-03 (maintainer, /quest-unblock of +[moq-dev/quest#423](https://github.com/moq-dev/quest/pull/423)'s wait to +install `quest` on Claude's `PATH`): + +- Install `quest` on the `PATH` Claude Code starts with? ✅ No (rejected: + `nix profile add github:moq-dev/quest` on every host). +- How should sessions reach a pinned `quest` and the rest of the dev shell? + ✅ A dev-shell hook in the maintainer's own Claude settings (rejected: hook + entries that `nix run` the repository's pinned input, as moq-dev/moq does; + dropping the hooks with no replacement). Reason: "We don't want to force + direnv+nix for other users"; repository config applies to every + contributor. +- Repository-level direnv `SessionStart` hooks in quest, moq, moq.pro, rind, + and rust2? ✅ Remove them from all five once the user-level hook loads the + dev shell (rejected: keep them). +- Quest's worktree hooks? ✅ Drop them from repositories, and `quest init` + stops writing hook entries; the hooks become an opt-in user-level setup + (rejected: this repository and moq only; this repository only; moving them + into the maintainer's settings). + [moq-dev/quest#423](https://github.com/moq-dev/quest/pull/423) is closed. +- Codex? ✅ A user-level Codex `SessionStart` hook does the same capture, and + the repositories' `.codex/` dev-shell hooks go (rejected: Claude only). +- A fresh worktree needs `direnv allow`: direnv keys each approval by the + SHA-256 of the `.envrc`'s absolute path and contents + (`~/.local/share/direnv/allow/`, 1,706 entries on the maintainer's host on + 2026-11-03), so nothing carries over from the main checkout. ✅ The + user-level hook allows a checkout itself when its `.envrc`, `flake.nix`, + and `flake.lock` match the remote's default branch (rejected: whitelisting + `~/work` and `~/worktrees` in `direnv.toml`, which would also evaluate a + fork PR's dev shell; keeping manual allows). A branch that changes them + still needs a manual allow. Amended 2026-11-03 (review of + [moq-dev/quest#458](https://github.com/moq-dev/quest/pull/458)): those + three files alone don't make a checkout reviewed. Any repository's fresh + clone matches its own default branch. A fork PR head can change another + file the dev shell runs, such as moq.pro's `dev/mbx-shim.sh`. Both were + auto-allowed in a reproduction against the merged hook. + Decided 2026-11-03 (maintainer, /quest-complete), after weighing the + risk as narrow: a session rooted at an outside contributor's fork head, + which the guide already says never to run, since agents check untrusted + heads out under `.tmp/` rather than as a session root, and an agent + session in any checkout runs its code anyway. ✅ One cheap guard: the + hook also requires `HEAD` to be on some `origin` branch + (`git for-each-ref --contains HEAD refs/remotes/origin/`), so a fork PR + head is never auto-allowed (rejected: an owner allowlist, a clean tree, + and a tracked `.envrc` on top; matching the whole tree to the merge base; + an owner allowlist alone; leaving it as is). Shipped in + [kixelated/dotfiles#35](https://github.com/kixelated/dotfiles/pull/35) + and live on `desktop` and `nixos2`, where a fresh worktree of moq-dev/moq + still loads and one on an unpushed commit is refused. +- AGENTS.md instructions to run tools through `nix develop`? ✅ Remove them in + all five repositories once the user-level hooks are on every host the + maintainer runs agents on (rejected: this repository only; keeping them as + a fallback). + +The user-level hooks live in [kixelated/dotfiles](https://github.com/kixelated/dotfiles), +installed into each Claude profile's `settings.json` and Codex's user +config, and generalize this repository's `.claude/hooks/direnv.sh` and +moq-dev/moq's `.codex/hooks/direnv.sh`. + +## Required + +- [quest init writes no hooks](/quest/m0/user-dev-shell/init-hooks.md) - `quest init` stops writing hook entries and removes the ones it or a wrapper wrote; the docs describe the hooks as user-level opt-in +- [Repositories drop dev-shell config](/quest/m0/user-dev-shell/repo-config.md) - quest, moq, moq.pro, rind, and rust2 drop their dev-shell hooks, Quest worktree hooks, and `nix develop` instructions diff --git a/quest/m0/user-dev-shell/init-hooks.md b/quest/m0/user-dev-shell/init-hooks.md new file mode 100644 index 00000000..e93d5158 --- /dev/null +++ b/quest/m0/user-dev-shell/init-hooks.md @@ -0,0 +1,42 @@ +# [S] quest init writes no hook entries + +## Goal + +`quest init` no longer writes Claude Code hook entries into a repository's +`.claude/settings.json`, and removes the `quest worktree hook` entries an +earlier version wrote, including ones a repository wrapped, such as +moq-dev/moq's `nix run "${CLAUDE_PROJECT_DIR}#quest" -- worktree hook`. +`quest uninstall` removes both forms too. The README and SETUP.md describe the +worktree hooks as an opt-in a user adds to their own Claude settings, with +the entries to add. + +## Plan + +Decided 2026-11-03 (maintainer, /quest-unblock): repository config applies +to every contributor, so Quest's hooks become user-level opt-in; see the +[epic](/quest/m0/user-dev-shell/README.md). + +- Replaces the wrapped-hooks quest (moq-dev/moq, 2026-11-03), which widened + `isQuestHook` (`src/core/init.ts`) so `init` would accept wrapped hooks. + For removal, a command hook is Quest's only when it runs Quest: the plain + `quest worktree hook`, or a wrapper whose command ends in `worktree hook` + and names the `quest` executable or package as a word or flake attribute, + such as moq's `nix run "${CLAUDE_PROJECT_DIR}#quest" -- worktree hook`. + Another tool's command that merely ends in `worktree hook` stays. +- `HOOK_EVENTS` and the hook plan in `src/core/init.ts` stop adding + entries; `init` reports each entry it removes. The enrollment setup PR + ([moq-dev/quest#431](https://github.com/moq-dev/quest/pull/431)) uses the + same plan, so it stops proposing hooks too. +- The `quest worktree hook` command stays, for users who opt in. +- Removing moq's four wrapped entries, whether `init` here or + [repo-config](/quest/m0/user-dev-shell/repo-config.md) does it first, ends + Quest's reclaim of Claude Code checkouts in moq. Nothing replaces it: the + maintainer's user-level setup (kixelated/dotfiles) loads only the dev + shell, and the epic's decision rejected moving Quest's hooks into the + maintainer's settings. So this quest doesn't wait on that setup, and the + same end state results in either order. A user who wants reclaim adds the + opt-in entries the docs describe. Any removal through the enrollment setup + PR shows in that PR's diff. +- Test `init` and `uninstall` on a repository with plain and wrapped + entries, and with other hooks beside them, which stay, including another + tool's command that ends in `worktree hook`. diff --git a/quest/m0/user-dev-shell/repo-config.md b/quest/m0/user-dev-shell/repo-config.md new file mode 100644 index 00000000..19bceb61 --- /dev/null +++ b/quest/m0/user-dev-shell/repo-config.md @@ -0,0 +1,31 @@ +# [S] Repositories drop their dev-shell config + +## Goal + +moq-dev/quest, moq, moq.pro, rind, and rust2 carry no dev-shell hooks and no +instructions to wrap commands in Nix: each drops its direnv `SessionStart` +hook from `.claude/settings.json` and `.codex/hooks.json` with the scripts +and tests behind them, moq drops its four Quest worktree hook entries, and +each AGENTS.md drops its `nix develop` instructions. Agents there still run +the pinned tools, through the maintainer's user-level hooks. One PR per +repository. + +## Plan + +Decided 2026-11-03 (maintainer, /quest-unblock); see the +[epic](/quest/m0/user-dev-shell/README.md). + +- Keep every other hook: moq.pro's `submodule.sh`, `worktree.sh` (its own + submodule-aware worktrees, not Quest's), and `tofu-secrets.py`. +- CONTRIBUTING.md's human setup (`nix develop` to match CI) stays; only the + agent-facing instructions go. In this repository, AGENTS.md's "`quest` + means `bun run --silent quest`" note stays, since it names which `quest` + to run, not how to load it. +- Check each repository from a fresh worktree on a host with the user-level + hooks: a new Claude Code session and a new Codex session both resolve the + dev shell's tools (`which just`, `quest --version` where pinned) with no + `nix develop` and no manual `direnv allow`. + +## Waiting + +- Maintainer: pull kixelated/dotfiles and run `bin/install-dev-shell.py` (or the full `install`, with `codex` on `PATH`) on `nixos` (`kixtop` has neither direnv nor Nix, so the hook has nothing to load there; it got only the effort defaults on 2026-11-03); it changes the live agent config on each host, which an agent here doesn't touch unasked. The repositories' hooks must not go before the user-level ones arrive. [kixelated/dotfiles#33](https://github.com/kixelated/dotfiles/pull/33) is merged. On 2026-11-03, `desktop` and `nixos2` had the hook in `~/.claude-personal/settings.json`, but `nixos` and `kixtop` didn't. Check on each host with direnv and Nix (`desktop`, `nixos2`, `nixos`) that `~/.claude-personal/settings.json` has the dev-shell `SessionStart` hook and that a fresh worktree's session resolves `just`; on `kixtop` the hook is an expected no-op diff --git a/quest/m0/worktree-hooks.md b/quest/m0/worktree-hooks.md deleted file mode 100644 index 0dd9d2eb..00000000 --- a/quest/m0/worktree-hooks.md +++ /dev/null @@ -1,29 +0,0 @@ -# [S] This repository installs its own worktree hooks - -## Goal - -Claude Code sessions in moq-dev/quest reclaim their subagents' checkouts -through `quest worktree hook`, as `quest init` sets up in other -repositories. `quest` is reliably available to Claude Code's hook -environment here, and on `PATH` for the merge driver, without running a PR -head's code. `quest init` then installs the hooks in this repository's -`.claude/settings.json`. - -## Plan - -Decided 2026-11-01 (user, /quest-plan of the follow-ups from the -2026-11-01 `/quest-complete` sessions): ✅ plan it, [S], m0. Sources: -[moq-dev/quest#350](https://github.com/moq-dev/quest/pull/350)'s decision 1a -and [moq-dev/quest#359](https://github.com/moq-dev/quest/pull/359). - -- [moq-dev/quest#350](https://github.com/moq-dev/quest/pull/350) shipped the - hooks but, by its decision 1a, didn't install them here: Claude Code runs - hooks in its own environment, not the direnv dev shell, and the merge - driver also needs `quest` on `PATH`. This repository's 65 leaked - worktrees (8.2 GB, 2026-10-23) were the measurement behind the hooks. -- The dev shell's `quest` is the reviewed `quest-pinned` build - ([moq-dev/quest#344](https://github.com/moq-dev/quest/pull/344)), while - `bun run --silent quest` runs the checkout. Choose how hooks reach a - pinned `quest` outside the dev shell, for example an installed release - that `SETUP.md` asks contributors for, and say what happens for a - contributor without one. diff --git a/quest/m0/worktree-routes.md b/quest/m0/worktree-routes.md index e0cbefbf..44ef6442 100644 --- a/quest/m0/worktree-routes.md +++ b/quest/m0/worktree-routes.md @@ -23,7 +23,9 @@ agent can't do, so its README description comes from Claude Code's own code: - The bridge never runs `git worktree unlock` on a hook-created checkout, since it records no Git root for one, so quest's lock stays in place. -Check it from a scratch repository set up with `quest init`, with +Check it from a scratch repository with the worktree hooks configured (the +opt-in user-level setup once +[quest init writes no hooks](/quest/m0/user-dev-shell/init-hooks.md) lands), with `QUEST_WORKTREE_HOME` isolated: start `claude remote-control --spawn worktree`, spawn a session from claude.ai, end it once without changes and once with an untracked file, and watch `quest worktree status`. Fix the README if it diff --git a/quest/m0/wrapped-hooks.md b/quest/m0/wrapped-hooks.md deleted file mode 100644 index 57bd248d..00000000 --- a/quest/m0/wrapped-hooks.md +++ /dev/null @@ -1,40 +0,0 @@ -# [XS] quest init recognizes wrapped worktree hooks - -## Goal - -`quest init` treats any command hook whose arguments end in `worktree hook` -as its own, such as moq-dev/moq's -`nix run "${CLAUDE_PROJECT_DIR}#quest" -- worktree hook`, which runs the -`quest` the repository pins. It needs no configuration. In a repository with -wrapped hooks, `quest init` then succeeds without changing them, and -`quest uninstall` removes them like the plain ones. - -## Plan - -Decided 2026-11-03 (maintainer, moq-dev/moq, planned through moq-dev/moq's -coordinator): - -- How should `quest init` recognize its hook when a repository wraps it? - - ✅ Match any command hook whose arguments end in `worktree hook` - - A configured hook command (rejected) - - No change (rejected) - -Milestone: m0, ranked after [Worktree hooks here](/quest/m0/worktree-hooks.md). -m0 is adoption on moq-dev's own repositories, and moq-dev/moq hits this -today. - -What happens: -[moq-dev/moq#7674](https://github.com/moq-dev/moq/pull/7674) runs moq's hooks -through its flake's pinned `quest`, so agents use the version moq pins. -`isQuestHook` (`src/core/init.ts`) matches only the exact `HOOK_COMMAND`, -`quest worktree hook`. So `quest init` counts the wrapped `WorktreeCreate` -and `WorktreeRemove` hooks as someone else's and refuses to run. The -workaround is to drop the hooks, run init, and restore them. - -Implementation guidance: - -- Widen `isQuestHook` to a command whose last two words are `worktree hook`. - A fresh install still writes the plain `HOOK_COMMAND`. -- `quest uninstall` (`src/cli/setup.ts`) uses the same check, so it removes - wrapped hooks too. Say so in its docs. -- Test a wrapped hook in init, re-init, and uninstall.