PreToolUse hooks in project .claude/settings.json are silently not registered

Status Open
Reported on v2.1.201
Maintainer reply None cached
Activity 0 comments · opened Jul 20, 2026

Claude Code version: 2.1.201

Summary: A PreToolUse hook defined in a project's .claude/settings.json never fires — the harness appears to drop the hooks key during config load while other keys in the same file (e.g. permissions) load and apply correctly.

Repro:

  1. Add to .claude/settings.json:
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Read|Edit|Write|Grep|Bash",
        "hooks": [
          { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/my-hook.sh\"" }
        ]
      }
    ]
  }
}
  1. Verify the script is valid standalone (e.g. pipe a matching PreToolUse JSON payload into it directly — it behaves correctly).
  2. Start a fresh claude session in the project (full process restart, not just a new chat) and trigger a matching tool call (e.g. Read on a path the hook should deny).
  3. The tool call succeeds; the hook script is never invoked (confirmed via temporary logging added at the top of the script — no log entries appear).

Diagnostics:

  • /hooks → PreToolUse: "No hooks configured for this event" (despite the config above being present and valid JSON).
  • claude --debug hooks startup log shows Hooks: Found 0 total hooks in registry, in the same startup sequence where Applying permission update: Adding N allow rule(s) to destination 'projectSettings' correctly reflects the permissions block from the identical settings.json file.

Expected: The hook should be registered and invoked on matching tool calls.

Actual: The hooks key from project .claude/settings.json is never registered into the runtime hook registry, while other keys (permissions) from the same file load normally.

Ruled out: stale in-process config cache (confirmed genuine restart via process start time vs. file mtimes), settings.local.json override (no hooks key present there), hooks-specific trust gate (no such flag in ~/.claude.json; general hasTrustDialogAccepted already true), cwd vs. repo-root discovery (settings.json is correctly discovered/watched regardless of cwd — confirmed via debug log Watching for changes in setting files ...).

View original on GitHub ↗