Updated after testing (see #118). The original version of this issue said the problem was hooks alone, that machines with disableAllHooks were not exposed, and suggested disableAllHooks as the fix. That was wrong. The checkout's env and CLAUDE.md load as well, and env can run code without any hook. The text below is corrected.
Summary
$cc:review and $cc:adversarial-review start Claude with -p inside a new worktree when they review a branch. The worktree checks out the commit under review, and a print-mode child doesn't ask for trust. Setting sources are not limited, so the checkout's project settings load:
- Hooks in
.claude/settings.json, such as SessionStart, run as soon as the review starts.
env reaches the processes the child starts. The review's git MCP server is node, so env: {"NODE_OPTIONS": "--require ./.claude/probe.cjs"} runs a file committed in the branch, even with disableAllHooks set.
CLAUDE.md and .claude/rules reach the model as instructions. So does AGENTS.md, which Claude Code 2.1.277 and later reads when a project has no CLAUDE.md.
So a branch under review can run code on the reviewer's machine and steer the reviewer, even though the review allowlist excludes Bash. --allowedTools limits tools, not settings.
Where (v1.5.0)
scripts/lib/review-worktree.mjs: createReviewIsolation / createReviewWorktree create the worktree, a detached checkout.
scripts/claude-companion.mjs: review and adversarial review run runClaudeReview in isolation.cwd.
scripts/lib/claude-cli.mjs: buildArgs sends -p ... --allowedTools ... --settings <preset>, with no --setting-sources.
Reproduction
Claude Code 2.1.283, macOS. Run the full companion (node scripts/claude-companion.mjs review --cwd <repo> --base main) against a local mock API with a fresh CLAUDE_CONFIG_DIR.
- The repo commits
.claude/settings.json (a SessionStart hook and the NODE_OPTIONS env above), .claude/probe.cjs and CLAUDE.md on main, then a code change on a feature branch.
- On v1.5.0 the hook runs, the probe file runs, and
CLAUDE.md reaches the model request.
- Adding
disableAllHooks: true to user settings stops only the hook.
Fix
Pass --setting-sources user to review children that run in a worktree. User settings, managed settings and the --settings file still load. Claude Code before 2.1.281 accepts the flag without applying it everywhere: nested .claude/rules files (fixed in 2.1.211), the command sandbox's filesystem configuration (2.1.246) and spawned sessions (2.1.281). Worktree reviews must refuse those versions. #118 does both.
Reviews that run in the user's own repository have the same exposure when an untrusted branch is checked out there:
- Working-tree reviews, which auto scope picks on any local change.
- The stop-review gate, which runs whenever a turn changes the working-tree fingerprint (the index, unstaged changes and untracked files). A turn that only checks out a branch with different content changes the index, so that alone is enough.
#118 leaves those unchanged and explains the trade-off.
Summary
$cc:reviewand$cc:adversarial-reviewstart Claude with-pinside a new worktree when they review a branch. The worktree checks out the commit under review, and a print-mode child doesn't ask for trust. Setting sources are not limited, so the checkout's project settings load:.claude/settings.json, such asSessionStart, run as soon as the review starts.envreaches the processes the child starts. The review's git MCP server isnode, soenv: {"NODE_OPTIONS": "--require ./.claude/probe.cjs"}runs a file committed in the branch, even withdisableAllHooksset.CLAUDE.mdand.claude/rulesreach the model as instructions. So doesAGENTS.md, which Claude Code 2.1.277 and later reads when a project has noCLAUDE.md.So a branch under review can run code on the reviewer's machine and steer the reviewer, even though the review allowlist excludes Bash.
--allowedToolslimits tools, not settings.Where (v1.5.0)
scripts/lib/review-worktree.mjs:createReviewIsolation/createReviewWorktreecreate the worktree, a detached checkout.scripts/claude-companion.mjs: review and adversarial review runrunClaudeReviewinisolation.cwd.scripts/lib/claude-cli.mjs:buildArgssends-p ... --allowedTools ... --settings <preset>, with no--setting-sources.Reproduction
Claude Code 2.1.283, macOS. Run the full companion (
node scripts/claude-companion.mjs review --cwd <repo> --base main) against a local mock API with a freshCLAUDE_CONFIG_DIR..claude/settings.json(a SessionStart hook and theNODE_OPTIONSenvabove),.claude/probe.cjsandCLAUDE.mdonmain, then a code change on a feature branch.CLAUDE.mdreaches the model request.disableAllHooks: trueto user settings stops only the hook.Fix
Pass
--setting-sources userto review children that run in a worktree. User settings, managed settings and the--settingsfile still load. Claude Code before 2.1.281 accepts the flag without applying it everywhere: nested.claude/rulesfiles (fixed in 2.1.211), the command sandbox's filesystem configuration (2.1.246) and spawned sessions (2.1.281). Worktree reviews must refuse those versions. #118 does both.Reviews that run in the user's own repository have the same exposure when an untrusted branch is checked out there:
#118 leaves those unchanged and explains the trade-off.