Launching from a subdirectory silently loads ZERO project hooks — root .claude/settings.json is never read (no walk-up, no warning)
Summary
Launching claude from a subdirectory of a project silently registers zero project hooks: the repo root's .claude/settings.json is never read, because Claude Code takes the launch cwd as the project directory and does not walk up (the way git resolves its toplevel).
For projects that rely on PreToolUse hooks as safety rails (custody gates, secret detectors, tenant-isolation guards), this means cd app && claude disarms every rail with no warning — the natural workflow of opening an agent in a subpackage defeats the project's entire hook surface.
Reproduction (probed on 2.1.206, macOS)
Probe hook with a hardcoded absolute command path, so path resolution is not a factor:
probe, ABSOLUTE cmd, settings at project root, launched from <root> → FIRED
probe, ABSOLUTE cmd, settings at project root, launched from <root>/lib → NEVER RAN
probe, ABSOLUTE cmd, settings in lib/.claude/, launched from <root>/lib → FIRED
So this is not the older "relative hook path exits 127" issue — from a subdirectory the settings file is never read at all, and ${CLAUDE_PROJECT_DIR} in hook commands cannot help (nothing registers to interpolate it).
Why this is worse than an error
A hook that fails makes noise. A hook that never registers is silent: the session behaves normally, every tool call proceeds, and nothing tells the user or the model that the project's guardrails are off. For teams using hooks as security/policy enforcement, subdirectory launch is an accidental (or trivially intentional) full bypass.
Expected behavior (any of these would resolve it)
- Walk up: resolve the project directory the way git resolves its toplevel — nearest ancestor with
.claude/(or.git), so a subdirectory launch loads the same project settings. - Warn loudly: if an ancestor directory contains
.claude/settings.jsonthat was NOT loaded, print a prominent startup warning ("project settings found at <path> were not loaded; launch from that directory to enable them"). - Config opt-in: a setting in user/managed scope (
projectRootDiscovery: "walk-up") for teams that want git-style resolution.
Workaround we ship today
A global (~/.claude/settings.json) PreToolUse guard on mutating tools that exits 2 when $CLAUDE_PROJECT_DIR has no .claude/settings.json but git -C "$CLAUDE_PROJECT_DIR" rev-parse --show-toplevel does — i.e. "project hooks exist but were never loaded." Works, but every team relying on hooks as rails has to independently discover this failure mode first.
Environment
- Claude Code 2.1.206 (behavior confirmed in shipped binary)
- macOS (darwin 25.5.0)
- Repro is trivial: any repo with a root
.claude/settings.jsonhook +cd <subdir> && claude