[BUG] PostToolUse hook stdout JSON output duplicated in terminal and context (re-filing #23381)
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?
This is a re-filing of #23381, which was incorrectly auto-closed as a duplicate of #16021. #16021 describes a different problem (file modification notes injected on every user message). This issue is specifically about PostToolUse hook output duplication triggered by stdout JSON.
Additionally, this report provides root cause isolation that #23381 did not include: the trigger is stdout JSON output, not the exit code or hook execution count.
PostToolUse hooks that output JSON to stdout (e.g., {"decision": "block", "reason": "..."}) have their output displayed twice in the terminal and injected as two identical <system-reminder> blocks into Claude's context. Hooks that use exit 2 + stderr are displayed once as expected. The hook script is only executed once — verified by logging PID to a file. The duplication occurs in Claude Code's output rendering/context injection, not in hook execution.
We tested three combinations to isolate the trigger:
| Exit code | Output method | Times displayed |
| --------- | ------------------------------------------ | --------------- |
| 0 | stdout JSON ({"decision": "block", ...}) | 2 (bug) |
| 2 | stderr (plain text) | 1 (correct) |
| 2 | stdout JSON ({"decision": "block", ...}) | 2 (bug) |
The trigger is stdout JSON output, regardless of exit code. Exit code 2 + stderr is the only combination that displays correctly.
What Should Happen?
Hook output should be displayed once in the terminal and injected as one <system-reminder> block into Claude's context, regardless of the output method (stdout JSON or stderr).
Error Messages/Logs
Terminal output (duplicated):
⎿ PostToolUse:Edit hook returned blocking error
⎿ Test: should appear once
⎿ PostToolUse:Edit hook returned blocking error
⎿ Test: should appear once
Steps to Reproduce
- Create
.claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/test-hook.sh"
}
]
}
]
}
}
- Create
.claude/hooks/test-hook.sh:
#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
[[ -z "$FILE_PATH" ]] && exit 0
jq -n '{"decision": "block", "reason": "Test: should appear once"}'
exit 0
chmod +x .claude/hooks/test-hook.sh
- Ask Claude to edit any file. The hook output appears twice.
- For comparison, replace the hook with exit 2 + stderr:
#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
[[ -z "$FILE_PATH" ]] && exit 0
echo "Test: should appear once" >&2
exit 2
This version displays once as expected.
Claude Model
Opus
Is this a regression?
Yes, this worked in a previous version
Last Working Version
Unknown. Related issues #9208 and #14281 were previously fixed but the problem has regressed.
Claude Code Version
2.1.63
Platform
Anthropic API
Operating System
Ubuntu/Debian Linux
Terminal/Shell
VS Code integrated terminal
Additional Information
Related issues:
- #23381 — Same symptom. Auto-closed as duplicate of #16021, but #16021 is a different problem (file modification notes injected on every user message). This issue is a re-filing with root cause isolation.
- #9208 — Duplicate PostToolUse:Edit Hook Calls (closed)
- #14281 — Hook additionalContext injected multiple times (closed)
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗