[BUG] PostToolUse hook stdout JSON output duplicated in terminal and context (re-filing #23381)

Resolved 💬 3 comments Opened Mar 4, 2026 by Di-Is Closed Apr 7, 2026

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

  1. Create .claude/settings.json:
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/test-hook.sh"
          }
        ]
      }
    ]
  }
}
  1. 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
  1. chmod +x .claude/hooks/test-hook.sh
  1. Ask Claude to edit any file. The hook output appears twice.
  1. 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)

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗