Skip to content

feat(init)!: write no hook entries; worktree hooks become a user-level opt-in - #466

Merged
moq-bot[bot] merged 10 commits into
mainfrom
quest/m0/user-dev-shell/init-hooks
Nov 4, 2026
Merged

moq-bot[bot] merged 10 commits into
mainfrom
quest/m0/user-dev-shell/init-hooks

Conversation

@kixelated

@kixelated kixelated commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

Finishes quest/m0/user-dev-shell/init-hooks.md: quest init writes no Claude Code hook entries, and Quest's worktree hooks become an opt-in each user adds to their own Claude settings, as the maintainer decided on 2026-11-03 (see quest/m0/user-dev-shell/README.md).

Decisions

Settled by the maintainer in /quest-complete on 2026-11-04, superseding the earlier "delete exact, warn others" pick. Recorded in the Design change comment.

  1. How should init, uninstall, and the Worker's setup PR handle hook entries already in a repository's .claude/settings.json? The maintainer: "Nobody is using this we dont need to cleanup."
    • ✅ No cleanup: quest init simply stops writing hook entries. init, uninstall, and the setup PR neither remove, detect, nor warn about existing entries. removeHooks, lookalikeHooks, HookEntry, the "removed/left" stderr reporting, the setup PR's hook lists, and their tests and docs go. The user-level opt-in docs (README JSON, SETUP.md opt-in step) stay.
    • Delete the exact quest worktree hook entries and warn about lookalikes (the previous pick).
    • A strict whole-command grammar.
    • Extend the matcher.
    • Merge as is.
  2. Does "no cleanup" drop uninstall's removal of the exact quest worktree hook that main has today?
    • ✅ Yes: nothing writes those entries any more, so uninstall no longer edits .claude/settings.json either. Nothing in the code or docs needs it: the Worker never uninstalls, and no other caller reads .claude/settings.json.
    • Keep uninstall's exact removal.
  3. How should moq's hand-written entries be removed? (Settled earlier, unchanged.)
    • ✅ Open a moq-dev/moq PR: moq-dev/moq#7889 deletes the 4 entries. Neither PR blocks the other.
    • A follow-up quest.
    • Leave it in place.

Changes

  • quest init no longer adds hooks to .claude/settings.json and never reads it, so a malformed or conflicting settings file no longer refuses init.
  • quest uninstall no longer edits .claude/settings.json.
  • Setup PR (Worker): it uses the same planInit, so it stops proposing hooks and stops fetching .claude/settings.json. The setup page, commit message, and cloudflare/GITHUB.md no longer mention hooks.
  • Docs: README's "Claude Code checkouts" section gives the JSON entries to merge into ~/.claude/settings.json. SETUP.md's Initialize step says init writes no Claude Code hooks, a new "Opt into worktree hooks" step offers them with the user's consent, and the Remove step says to drop the user-level entries once quest is gone. quest worktree hook --help, quest init --help, and quest uninstall --help match.
  • Cleanup: HOOK_EVENTS moves to src/cli/worktree-hook.ts, its only consumer. CLAUDE_SETTINGS, HOOK_COMMAND, isQuestHook, parseSettings, renderSettings, and Settings are deleted.
  • Quest tree: deletes the quest and its links. Rewords the prose that linked it in quest/m0/session-checkouts.md (whose SessionStart/SessionEnd entries extend the README's JSON block and HOOK_EVENTS), quest/m0/worktree-routes.md, and quest/m2/hook-path-error.md, which is now unblocked. Amends m0's static-stubs decision in quest/m0/README.md, which still listed hook entries among what init installs. quest/m0/user-dev-shell/repo-config.md and the epic's Required line no longer include moq's worktree hook entries, which moq-dev/moq#7889 removes.

Public API

Breaking for quest init: it no longer installs Claude Code worktree hooks, and quest uninstall no longer removes them. Existing entries in a repository's .claude/settings.json stay as they are. Migration: a user who wants Quest to manage Claude Code's worktrees merges the README's JSON entries into ~/.claude/settings.json, and a repository that carries entries removes them by hand, as moq does in moq-dev/moq#7889. quest/core drops the exports CLAUDE_SETTINGS, HOOK_COMMAND, HOOK_EVENTS, isQuestHook, parseSettings, renderSettings, and Settings; the Worker is the only consumer. No wire impact.

Choices made without asking

  • The opt-in is documented at user level only (~/.claude/settings.json), not .claude/settings.local.json. A local settings file is untracked, so it is missing from every checkout under .worktrees/.
  • SETUP.md gains an opt-in step that asks for consent before editing ~/.claude/settings.json, since SETUP.md is a script agents follow.
  • One test pins the behavior: init creates no .claude/settings.json, and init and uninstall leave an existing one byte for byte, including an exact quest worktree hook entry. The CLI and Worker tests check that neither the printed paths nor the setup PR include the file.

Validation

  • just check: passed on 21e0bf33.
  • just test: passed on 21e0bf33 (vitest 1049 passed, 5 skipped; Worker 531 passed; shell suites and both lifecycle checks passed).

Review

mechanical fix by codex / Codex: Merged base and re·solved the hook header conflict, preserving the PR comment and base import removal; passed bun run build, bun run check, bun run test (root), and bun run build, bunx tsc --noEmit, bun run test (cloudflare; CLOUDFLARE_SEND_METRICS=false). (head 1bfdb8f)

Codex gpt-6.1-sol at xhigh effort, new session 01a1a7b3 (started with --new after the design change), round 1 against origin/main at de0e7e4e. Reviewed head: 21e0bf33. No findings.

The previous sessions (01a1a56b on the heuristic matcher and 01a1a772 on exact removal with lookalike warnings) are superseded: the code they reviewed is deleted.

Suggested follow-ups

  • Plan it: quest worktree should keep .worktrees/ out of git status. Now that the hooks are a user-level opt-in, they apply in every repository the user opens, and Quest puts Claude's checkouts under each repository's .worktrees/. A repository that doesn't ignore that directory then shows ?? .worktrees/ in its main checkout's git status. Fix: when quest worktree creates the first checkout, add /.worktrees/ to $GIT_COMMON_DIR/info/exclude if nothing ignores it yet. [S]

🤖 Generated with Claude Code

(Written by Claude Opus 5.5)

kixelated and others added 2 commits November 3, 2026 22:57
…ttings

`quest init` no longer adds Claude Code hook entries to
`.claude/settings.json`; it removes the `quest worktree hook` entries earlier
versions wrote, including wrappers that run Quest such as
`nix run "${CLAUDE_PROJECT_DIR}#quest" -- worktree hook`, and names each on
stderr. `quest uninstall` removes both forms through the same match. The
README and SETUP.md describe the hooks as a user-level opt-in with the
entries to add.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title quest init writes no hook entries feat(init)!: write no hook entries; worktree hooks become a user-level opt-in Nov 4, 2026
kixelated and others added 3 commits November 3, 2026 23:23
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… lookalikes

The maintainer rejected the heuristic matcher (round-4 design checkpoint):
Quest only ever wrote the exact `quest worktree hook`, so init and uninstall
now delete only that command, on any event. Any other command hook ending in
`worktree hook`, such as moq-dev/moq's `nix run ... -- worktree hook`, may be
another tool's, so it stays and gets a stderr warning naming the event,
command, and file; the setup PR lists it under "Left out".

Drops QUEST_COMMAND and its launcher grammar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

Design change: the maintainer settled the round-4 design checkpoint in /quest-complete on 2026-11-04, rejecting the heuristic matcher's premise ("I feel like we're doing something wrong here").

Facts behind it: Quest only ever wrote the exact command quest worktree hook (HOOK_COMMAND, matched exactly by isQuestHook on main). The only wrapped form anyone carries is moq-dev/moq's hand-written nix run "${CLAUDE_PROJECT_DIR}#quest" -- worktree hook, in 4 entries of its .claude/settings.json.

  1. How should init/uninstall handle existing repository Quest hooks? ✅ Delete exact, warn others. init and uninstall delete only command hooks whose command is exactly quest worktree hook, on every event, and still clean up the groups, events, and file they leave empty. Any other command hook ending in worktree hook is never deleted. Init and uninstall warn about it on stderr, naming the event, command, and file, and point to the README's opt-in section. A false match then costs one warning line instead of deleting another tool's hook. QUEST_COMMAND and its wrapper/launcher grammar go, along with the round-4 findings at src/core/init.ts:269,271. The Worker's setup PR names the hooks it deletes and lists the warned ones under "Left out". Rejected: delete nothing and only warn; a strict whole-command grammar; exact plus moq's literal string; extending the current pattern; merging as is.
  2. How should moq's hand-written entries be removed? ✅ Open a moq-dev/moq PR, moq-dev/moq#7889. It deletes the 4 entries and links this PR. Neither PR blocks the other. Rejected: a follow-up quest; leaving it to init's warning.

Implemented in 8d8d2fc. Reviews restart with quest review --new.

(Written by Claude Opus 5.5)

@kixelated
kixelated marked this pull request as ready for review November 4, 2026 16:51
@coderabbitai

coderabbitai Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Important

  • Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ab063cce-2ccc-4e0b-a9b4-598fba0ef13d
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

Reviewed the whole PR head d6e260d (7 commits, 20 files) against main at e102ec7, reading the full diff and verifying the surrounding code (planInit/checkPath, the Checkout implementations, planned() and the setup service, the CLI warn contract) plus the test suites cover each claim in the PR body.

  • quest init writes no hooks — planInit drops installHooks and instead calls removeHooks on existing settings, returning the removed entries inside the InitChange for .claude/settings.json.
  • Exact-removal with lookalike warnings — removeHooks deletes only handlers whose command is exactly quest worktree hook, on every event, then the groups/events/file they empty; lookalikeHooks reports other command hooks ending in worktree hook so init and uninstall warn instead of touching them.
  • CLI reporting — init() now returns InitChange[]; main warns removed …/left … lines on stderr and lists only changed paths on stdout; uninstall applies the same removal.
  • Cloudflare setup PR — planned() still fetches .claude/settings.json, so the Worker removes Quest's entries and lists lookalikes under "Left out", rendered through code() for untrusted text; the page and commit message no longer mention hooks.
  • Docs and quest tree — README gives the user-level JSON opt-in, SETUP.md gains the consent-gated "Opt into worktree hooks" step and the uninstall note, GITHUB.md and the projects page match, the init-hooks quest is deleted with its links reworded everywhere.

Verification notes:

  • I checked the two guarantees the docs make about symlinks still hold: the Worker's treeCheckout.stat throws InitRefused on any symlink prefix, including .claude/settings.json itself, so a linked settings file is still refused there; and checkPath on that path used stat (link-following) before, so local behavior is unchanged. The stale-looking checkPath drop is fine.
  • removeHooks handles the __proto__ event-key hazard correctly (rebuilt with Object.fromEntries), preserves CRLF/indent, and the kept/remaining bookkeeping matches what the tests pin down: I ran test/init.test.ts and test/cli.test.ts here — 78 passed.
  • The Worker's end-to-end hook-removal test could not run in this environment (the cloudflare/ workspace's node_modules isn't installed here) — treat that as covered by CI, which the PR body reports green at these commits.

Pullfrog  | View workflow run | Using glm-5.3-flash | 𝕏

kixelated and others added 2 commits November 4, 2026 09:52
The maintainer decided no cleanup: nobody carries the entries earlier
versions wrote. init writes no hooks, and neither init, uninstall, nor the
Worker's setup PR removes, detects, or warns about existing entries in
.claude/settings.json. removeHooks, lookalikeHooks, HookEntry, the
stderr reporting, the setup PR's hook lists, and uninstall's exact
removal go, along with their tests and docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

Design change: the maintainer settled hook cleanup in /quest-complete on 2026-11-04, superseding the previous "delete exact, warn others" decision. The maintainer: "Nobody is using this we dont need to cleanup."

  1. ✅ No cleanup: quest init simply stops writing hook entries. init, uninstall, and the Worker's setup PR neither remove, detect, nor warn about existing hook entries in .claude/settings.json. removeHooks, lookalikeHooks, HookEntry, the "removed/left" stderr reporting, the setup PR's hook lists, and their tests and docs are deleted. The user-level opt-in docs (README JSON, SETUP.md opt-in step) stay. Rejected: deleting the exact entry and warning about lookalikes; the strict wrapper grammar; extending the matcher; merging as is.
  2. ✅ "No cleanup" includes uninstall: it drops the exact quest worktree hook removal main has today, since nothing writes those entries any more. Nothing in the code or docs needed it (the Worker never uninstalls), so no narrower version was kept.

Implemented in 21e0bf3. Reviews restarted with quest review --new (Codex session 01a1a7b3, round 1: no findings).

(Written by Claude Opus 5.5)

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

Reviewed the delta since the prior pullfrog review (d6e260d): the merge of origin/main (364c25b) and commit 21e0bf3, which implements the maintainer's "no cleanup" decision — init and uninstall now leave .claude/settings.json entirely alone.

  • planInit (src/core/init.ts) dropped installHooks, the checkPath on .claude/settings.json, and all settings parsing; init neither reads nor writes the file, and conflicts in it no longer refuse init.
  • src/cli/setup.ts deleted uninstallHooks; uninstall no longer edits .claude/settings.json.
  • The hook API left quest/core entirely (CLAUDE_SETTINGS, HOOK_COMMAND, HOOK_EVENTS, HookEntry, Settings, isQuestHook, parseSettings, renderSettings); HOOK_EVENTS moved to src/cli/worktree-hook.ts, its only consumer.
  • The Cloudflare setup PR no longer fetches or names hook entries: planned() dropped .claude/settings.json, the hookList/lookalikes plumbing and their tests are gone, and the commit message and setup page copy updated.
  • Tests pin the new contract: init creates no .claude/settings.json, and init and uninstall leave an existing one byte-for-byte, including an exact quest worktree hook entry; the CLI test asserts the file never appears in output. The Worker's symlink refusal is still covered, retargeted to .claude/skills.
  • Docs and quest tree aligned: README's user-level opt-in JSON, SETUP.md's opt-in and remove steps, GITHUB.md, the projects page, the quest/m0/README.md amendment, and the now-unblocked quest/m2/hook-path-error.md.

Verified locally on 21e0bf3: tsc --noEmit clean and test/init.test.ts + test/cli.test.ts (72 tests) green. The cloudflare/ workspace's node_modules is absent in this environment, so its changes are covered by CI (reported green on this head).

Pullfrog  | View workflow run | Using glm-5.3-flash | 𝕏

@kixelated
kixelated added this pull request to the merge queue Nov 4, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Nov 4, 2026
@moq-bot

moq-bot Bot commented Nov 4, 2026

Copy link
Copy Markdown

quest land watch: the merge queue removed this pull request at 21e0bf3 because it conflicts with main (merge_conflict).

The land fixer works on it in the background, and the watcher re-enqueues it at the back of the queue once it is fixed or judged flaky, or hands it back here with what it needs.

(written by quest land watch)

@moq-bot
moq-bot Bot enabled auto-merge November 4, 2026 19:03
@moq-bot

moq-bot Bot commented Nov 4, 2026

Copy link
Copy Markdown

quest land watch: armed auto-merge on this pull request at 1bfdb8f, so it joins the back of the merge queue once its required checks pass, with the land fixer's fix (codex / Codex): Merged base and re·solved the hook header conflict, preserving the PR comment and base import removal; passed bun run build, bun run check, bun run test (root), and bun run build, bunx tsc --noEmit, bun run test (cloudflare; CLOUDFLARE_SEND_METRICS=false)..

(written by quest land watch)

@moq-bot
moq-bot Bot added this pull request to the merge queue Nov 4, 2026

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

Reviewed the delta since the prior pullfrog review (21e0bf3): only the merge commit 1bfdb8f, which merged origin/main at 42e3ad1 and resolved the conflict in the Git-call plumbing.

  • src/cli/setup.ts switched from the PR branch's local gitOutput (./claims) to the shared git helper from ./git, which additionally strips inherited repository-local variables (GIT_DIR, GIT_WORK_TREE, GIT_INDEX_FILE, …) via discoveryEnv() — the convention main landed in #478. Call sites (register, unregister) are otherwise unchanged; the shared helper's config-missing-returns-empty and error-shape behavior matches what those call sites already expect.
  • src/cli/worktree-hook.ts dropped its private git helper (which already did discoveryEnv() + trim) for the same shared git. All call sites keep the same two-argument form with no input, and the hook's waiting()/Busy retry loop is untouched.
  • Nothing else in the PR changed versus the reviewed head; tsc --noEmit is clean at 1bfdb8f and CI's build check passed on it.

Pullfrog  | View workflow run | Using glm-5.3-flash | 𝕏

Merged via the queue into main with commit 690bc25 Nov 4, 2026
18 checks passed
@moq-bot
moq-bot Bot deleted the quest/m0/user-dev-shell/init-hooks branch November 4, 2026 19:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant