Per-turn command hooks (PreToolUse/UserPromptSubmit/Stop) don't fire in non-interactive modes (VS Code extension, `claude -p`) — only SessionStart fires
Summary
User-level settings.json command hooks for the per-turn events — PreToolUse, UserPromptSubmit, Stop (and PostToolUse) — do not execute in non-interactive runtimes: the VS Code extension agent panel and claude -p headless mode. SessionStart hooks do fire in the same runtimes. The hooks are clearly loaded (the settings.json permissions rules are parsed/enforced and SessionStart runs), so this is not a "config not found" problem — the per-turn events simply never invoke their commands.
Environment
- Claude Code 2.1.181
- Windows 11
- Reproduced in both: the VS Code extension (Agent SDK), and standalone
claude -pheadless mode.
Minimal repro
- Add a
PreToolUsecommand hook to~/.claude/settings.jsonthat writes a marker to a fixed path (adapt the command to your OS):
``json``
"hooks": {
"PreToolUse": [
{ "matcher": "Bash", "hooks": [
{ "type": "command", "command": "sh -c 'echo fired >> \"$HOME/hook-fire-test.log\"'" }
] }
]
}
- Trigger a tool call:
claude -p "Use the Bash tool to run exactly: echo hi" --allowedTools Bash - Observed: the bash command runs (
hiis printed), but~/hook-fire-test.logis never written —PreToolUsedid not fire. - Control: the same write-a-marker command under
SessionStartdoes fire.
(Test with --dangerously-skip-permissions off — that flag additionally disables the hook system, which is expected.)
Impact
Any automation that relies on per-turn hooks — memory capture, audit/permission gates, contradiction checks, telemetry — silently does nothing in these modes. There is no error and no log: the tool just runs without the hook. It is very hard to diagnose because the hook looks correctly configured and SessionStart works, so everything appears wired.
Expected
Per-turn command hooks (PreToolUse / UserPromptSubmit / Stop / PostToolUse) fire in non-interactive modes (VS Code extension, claude -p, Agent SDK) the same as in the interactive CLI — or the docs clearly state which hook events fire in which runtimes.