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

Offen
#94,275 1 Kommentar 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen
area:hooks bug has repro
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

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.