[BUG] SessionStart hook stdout silently dropped despite valid JSON output and exit code 0

Resolved 💬 3 comments Opened Dec 11, 2025 by coygeek Closed Dec 27, 2025

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?

SessionStart hooks that output valid JSON with the documented hookSpecificOutput.additionalContext structure have their stdout silently dropped by Claude Code. The hook executes successfully (exit code 0, valid JSON output), but the additionalContext content never appears in Claude's context.

Other SessionStart hooks in the same settings.json configuration work correctly - their output IS captured. Only certain hooks have their output dropped, despite following identical patterns.

What Should Happen?

Per the hooks documentation, SessionStart hooks with exit code 0 should have their stdout added to Claude's context:

Exit code 0: Success. For SessionStart hooks, stdout is added to context.

The hook outputs:

{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "CLAUDE.md: 45,466 chars (limit: 40,000, overage: +5,466)..."
  }
}

This should appear in Claude's system context, but it does not.

Error Messages/Logs

No error messages. The hook appears to succeed silently.

**Hook execution log** (`~/.claude/hooks/hooks.log`):

2025-12-10 22:30:54.710 [INFO] [analyze_claude_md] starting
2025-12-10 22:30:54.710 [INFO] [analyze_claude_md] python=3.14.1
2025-12-10 22:30:54.712 [INFO] [analyze_claude_md] stdin_len=329
2025-12-10 22:30:54.712 [INFO] [analyze_claude_md] input_keys=['session_id', 'transcript_path', 'cwd', 'hook_event_name', 'source']
2025-12-10 22:30:54.713 [INFO] [analyze_claude_md] claude_md=/Users/user/.claude/hooks/analyze-prompt-complexity-on-submit/CLAUDE.md
2025-12-10 22:30:54.713 [INFO] [analyze_claude_md] total_chars=45466
2025-12-10 22:30:54.723 [INFO] [analyze_claude_md] stdout_len=1340
2025-12-10 22:30:54.723 [INFO] [analyze_claude_md] additionalContext_len=1223
2025-12-10 22:30:54.723 [INFO] [analyze_claude_md] duration_ms=14.1
2025-12-10 22:30:54.723 [INFO] [analyze_claude_md] stdout_printed=true
2025-12-10 22:30:54.723 [INFO] [analyze_claude_md] exit_code=0


**Session JSONL verification**:

# Zero matches for hook output content
$ grep -c "overage\|CLAUDE.md:.*chars\|Execute trim" session.jsonl
0

Steps to Reproduce

  1. Create a SessionStart hook that outputs valid JSON:
#!/usr/bin/env python3
"""SessionStart hook that outputs additionalContext."""
import json
import sys

def main():
    # Read stdin (Claude Code provides session info)
    stdin_data = sys.stdin.read()

    # Output valid JSON with additionalContext
    result = {
        "hookSpecificOutput": {
            "hookEventName": "SessionStart",
            "additionalContext": "TEST: This message should appear in Claude's context."
        }
    }
    print(json.dumps(result), flush=True)
    sys.exit(0)

if __name__ == "__main__":
    main()
  1. Configure in ~/.claude/settings.json:
{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 /path/to/test_hook.py"
          }
        ]
      }
    ]
  }
}
  1. Start a new Claude Code session
  1. Check the session JSONL file for "TEST: This message should appear"

Expected: The text appears in a <system-reminder> tag in Claude's context

Actual: The text is not present anywhere in the session transcript

Claude Model

None

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.0.65

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Terminal.app (macOS)

Additional Information

Environment

  • Claude Code version: 2.0.65
  • OS: macOS Darwin 25.1.0
  • Python: 3.14.1

Key observations

  1. Other hooks work: In the same session, session_id_display.py (another SessionStart hook) has its output correctly captured. The <Current Session> tag appears in Claude's context.
  1. Manual execution works: Running the hook manually produces valid JSON:
$ echo '{}' | python3 analyze_claude_md.py
{"hookSpecificOutput": {"hookEventName": "SessionStart", "additionalContext": "CLAUDE.md: 45,466 chars..."}}
  1. Format is identical: Both the working hook and failing hook use the same JSON structure with hookSpecificOutput.additionalContext.
  1. Timing is fast: Hook completes in ~14ms, well under any timeout.
  1. No stderr output: Hook produces no errors.

Working hook for comparison

This hook's output IS captured:

# session_id_display.py - OUTPUT IS CAPTURED
result = {
    "hookSpecificOutput": {
        "hookEventName": "SessionStart",
        "additionalContext": f"<Current Session>\nSession ID: {session_id}...\n</Current Session>"
    }
}
print(json.dumps(result), flush=True)
sys.exit(0)

Failing hook

This hook's output is NOT captured (identical structure):

# analyze_claude_md.py - OUTPUT IS DROPPED
result = {
    "hookSpecificOutput": {
        "hookEventName": "SessionStart",
        "additionalContext": "CLAUDE.md: 45,466 chars (limit: 40,000, overage: +5,466)..."
    }
}
print(json.dumps(result), flush=True)
sys.exit(0)

Hypothesis

There may be an undocumented size limit, character filter, or race condition affecting which SessionStart hook outputs are captured. The failing hook outputs ~1,340 bytes of JSON with ~1,223 characters of content.

Workaround

Currently investigating having the hook directly modify files instead of relying on stdout capture, but this defeats the purpose of the hook system's context injection.

View original on GitHub ↗

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