Skip to content

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

Closed

Description

@coygeek

Preflight Checklist

  • I have searched existing issues and this hasn't been reported yet
  • This is a single bug report (please file separate reports for different bugs)
  • 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

  2. 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.

  2. 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.

  2. Timing is fast: Hook completes in ~14ms, well under any timeout.

  3. 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.

Activity

  1. coygeek commented on Dec 11, 2025

    @coygeek
    Author

    Follow-up: Additional Investigation Findings

    After extensive debugging, I've ruled out several potential causes and narrowed down the issue.

    What I Verified Is NOT The Cause

    Hypothesis Investigation Result
    Missing XML tags Added <CLAUDE.md-Analysis> wrapper tags matching working hooks Still fails
    Output size limit Working hooks output up to 8,976 chars; failing hook outputs 1,266 chars Size is not the issue
    Invalid JSON format Compared byte-for-byte with working hooks Identical structure
    stderr interference Captured stderr separately Empty (0 bytes)
    Special characters Examined with od -c Only ASCII + newlines
    Timeout Hook completes in 14ms Well under any limit
    Trailing newlines Checked output format Single line, no trailing newline (same as working hooks)
    Hook execution order 5th of 10 SessionStart hooks No obvious conflict

    Detailed Comparison: Working vs Failing Hook

    Working hook (session_id_display.py):

    result = {
        "hookSpecificOutput": {
            "hookEventName": "SessionStart",
            "additionalContext": f"<Current Session>\nSession ID: {session_id}...\n</Current Session>"
        }
    }
    print(json.dumps(result), flush=True)
    sys.exit(0)
    • Output size: 8,976 chars
    • Execution time: ~2ms
    • Result: Captured ✓

    Failing hook (analyze_claude_md.py):

    result = {
        "hookSpecificOutput": {
            "hookEventName": "SessionStart",
            "additionalContext": "<CLAUDE.md-Analysis>\nCLAUDE.md: 45,466 chars...\n</CLAUDE.md-Analysis>"
        }
    }
    print(json.dumps(result), flush=True)
    sys.exit(0)
    • Output size: 1,266 chars
    • Execution time: ~14ms
    • Result: Silently dropped ✗

    Hook Execution Timeline

    All SessionStart hooks run in parallel. From hooks.log:

    22:52:31.606 [mcp-doc-sync] starting
    22:52:31.805 [session_id_display] starting
    22:52:31.808 [date_awareness] starting
    22:52:31.815 [analyze_claude_md] starting
    22:52:31.815 [analyze_claude_md] python=3.14.1
    22:52:31.818 [analyze_claude_md] total_chars=45466
    22:52:31.828 [analyze_claude_md] stdout_len=1385
    22:52:31.829 [analyze_claude_md] stdout_printed=true
    22:52:31.829 [analyze_claude_md] exit_code=0
    

    The failing hook:

    • Started at 22:52:31.815
    • Printed valid JSON at 22:52:31.829
    • Exited successfully at 22:52:31.829
    • Total duration: 14ms

    Raw Output Verification

    $ echo '{}' | python analyze_claude_md.py 2>/dev/null | jq .
    {
      "hookSpecificOutput": {
        "hookEventName": "SessionStart",
        "additionalContext": "<CLAUDE.md-Analysis>\nCLAUDE.md: 45,466 chars..."
      }
    }
    
    $ echo '{}' | python analyze_claude_md.py >/tmp/stdout.txt 2>/tmp/stderr.txt
    $ wc -c /tmp/stdout.txt /tmp/stderr.txt
        1386 /tmp/stdout.txt
           0 /tmp/stderr.txt

    Conclusion

    The hook produces valid output that is indistinguishable in format from working hooks. The issue appears to be in Claude Code's hook output collection mechanism, not in the hook itself.

    Possible areas to investigate in Claude Code internals:

    1. Race condition in parallel hook stdout collection
    2. Buffer handling when multiple hooks complete near-simultaneously
    3. Some undocumented filtering based on content patterns
    4. Hook output aggregation logic dropping certain outputs

    Environment

    • Claude Code: 2.0.65
    • macOS Darwin 25.1.0
    • Python 3.14.1
  2. coygeek commented on Dec 27, 2025

    @coygeek
    Author

    ✅ EMPIRICALLY VERIFIED: SessionStart additionalContext WORKS (2025-12-26, v2.0.76)

    Real Test Performed:

    # 1. Created test hook that outputs additionalContext:
    cat /tmp/hook_tests/session_start_context.sh
    # Output: {"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"TEST_MARKER_SESSION_12345: This marker tests SessionStart additionalContext injection."}}
    
    # 2. Added hook to settings.json and ran fresh Claude Code session:
    npx @anthropic-ai/claude-code@2.0.76 -p "Reply with ONLY: I see TEST_MARKER_SESSION_12345 if you see that string. Otherwise: NO MARKER FOUND"
    # Result: "I see TEST_MARKER_SESSION_12345"
    
    # 3. Removed hook and ran again:
    # Result: "NO MARKER FOUND"

    Empirical Evidence:

    Test Condition Claude's Response
    Hook configured with TEST_MARKER "I see TEST_MARKER_SESSION_12345"
    Hook removed "NO MARKER FOUND"

    Conclusion: SessionStart additionalContext is CONFIRMED WORKING in v2.0.76. GitHub issue #13650 is either fixed or was version-specific.

  3. github-actions commented on Jan 3, 2026

    @github-actions

    This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.

  4. locked as resolved and limited conversation to collaborators on Jan 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:toolsbugSomething isn't workinghas reproHas detailed reproduction stepsplatform:macosIssue specifically occurs on macOS

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions