anthropics / anthropics/claude-code
[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
- Vorherrschende Sprache
- Python
- Sterne
- 145k
- Forks
- 23.1k
- PR-Merge-Kennzahlen
- PR-Kennzahlen ausstehend
Beschreibung
### Preflight Checklist
- [x] I have searched [existing issues](https://github.com/anthropics/claude-code/issues?q=is%3Aissue%20state%3Aopen%20label%3Abug) 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 the
`system/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?
1. `hook_started` / `hook_response` should be emitted for every hook event type, not just
SessionStart, matching what #27200 requested.
2. `system/init` should 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
```shell
# 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
1. 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
```
2. 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
```
3. 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
```
4. 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 user` and `--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.
Beitragsleitfaden
Für dieses Repository ist kein Beitragsleitfaden indexiert
Rechercherichtung
Start by reproducing the `claude -p` command with `--output-format stream-json --verbose` and the project `.claude/settings.json` hook configuration. Compare `system/init` and hook event output for SessionStart and PreToolUse, then verify that every configured hook event is observable before the first tool call and that the relevant sources are listed.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Bewertung
- Tech-Stack
- python
- Bereich
- cli, observability, security
- Issue-Typ
- Bug
- Schwierigkeit
- 4/5
- Geschätzter Aufwand
- 3-5 Tage
- Aktivitätsstatus
- Aktiv
- Klarheit
- Größtenteils klar
- Anfängerfreundlichkeit
- 48/100