[BUG] stream-json emits hook_started/hook_response for SessionStart only; PreToolUse/PostToolUse/UserPromptSubmit/Stop hooks run with no event, so SDK consumers cannot see repo-supplied hooks execute
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
With --output-format stream-json --verbose, system/hook_started and system/hook_response
events are emitted only for SessionStart hooks. Hooks on every other event execute normally
(side effects are observable) but produce no event in the stream, nothing on stderr, and thesystem/init message has no field listing configured hooks.
Measured across 36 claude -p runs on 2.1.270 with hooks configured on PreToolUse, PostToolUse,
UserPromptSubmit, Stop, SessionEnd and SessionStart (both user-level and project-level):
$ cat results/*.jsonl | jq -r 'select(.type=="system" and (.subtype|startswith("hook"))) | .subtype+" "+.hook_event' | sort | uniq -c
36 hook_response SessionStart
36 hook_started SessionStart
Why this matters beyond debugging: project .claude/settings.json hooks are loaded and executed in-p mode without any trust check (documented), and in -p mode there is no dialog. So an SDK
consumer that runs claude -p in a directory it did not author gets arbitrary command execution
on the first tool call, with no signal at any layer it can observe. In an interactive session the/cd dialog at least warns "This directory configures hooks that run commands"; the SDK stream
gives nothing equivalent. #27200 asked for the same thing purely as an observability feature;
this report adds the untrusted-directory angle because it changes the severity.
Confirmed with a marker hook: PreToolUse hook in a never-trusted repo's .claude/settings.json
fired (marker file written at 12:00:03Z) while the stream contained only the two SessionStart events.
What Should Happen?
hook_started/hook_responseshould be emitted for every hook event type, not just
SessionStart, matching what #27200 requested.
system/initshould list the hook sources that were loaded for the session (at minimum: which
settings file, which event, matcher, and command string), so a consumer can inspect what will run
before the first tool call.
Error Messages/Logs
# system/init keys on 2.1.270 (no hook-related field):
["agents","analytics_disabled","apiKeySource","capabilities","claude_code_version","cwd",
"fast_mode_disabled_reason","fast_mode_state","mcp_servers","memory_paths",
"messaging_socket_path","model","output_style","permissionMode","plugins",
"product_feedback_disabled","session_id","skills","slash_commands","subtype",
"terminal_slash_commands","tools","type","uuid"]
# stderr of the run: empty
# marker log written by the PreToolUse hook:
STANDALONE_HOOK 2026-09-14T12:00:03Z
Steps to Reproduce
- Create a fresh git repo that has never been trusted:
``sh``
mkdir -p /tmp/hookrepo/.claude && cd /tmp/hookrepo && git init -q
cat > .claude/settings.json <<'JSON'
{"hooks":{"PreToolUse":[{"matcher":"","hooks":[{"type":"command","command":"touch /tmp/hookrepo/HOOK_FIRED"}]}]}}
JSON
- Run non-interactively with stream output:
``sh``
claude -p 'Use the Bash tool to run exactly: ls -la . Then reply DONE.' \
--allowedTools 'Bash(ls:*)' --output-format stream-json --verbose > out.jsonl 2> err.txt
- Observe:
``sh``
ls /tmp/hookrepo/HOOK_FIRED # exists: hook ran
jq -r 'select(.type=="system") | .subtype+" "+(.hook_event//"")' out.jsonl
# init
# hook_started SessionStart (only if you have user-level SessionStart hooks)
# hook_response SessionStart
# -> no event for the PreToolUse hook
cat err.txt # empty
- For comparison, add a SessionStart hook to the same file and rerun: it produces
hook_started/hook_response events.
Claude Model
Other
Is this a regression?
No, this never worked
Last Working Version
n/a
Claude Code Version
2.1.270
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Non-interactive/CI environment
Additional Information
--setting-sources userand--settings '{"disableAllHooks":true}'both prevent the project
hook from running, so mitigations exist; the gap is that a consumer has no way to detect that
it needed one.
- Docs (Hooks reference, "Hook lifecycle" and SDK "stream-json" message types) don't mention that
only SessionStart emits events; if this is intended, a doc note would help, but the consistency
between event types suggests it isn't.
- Related closed reports: #27200 (duplicate-closed), #15066 (stale-closed).
- Full reproduction fixtures and per-run transcripts available on request.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗