Problem
Advisory content from SessionStart (and other lifecycle) handlers is visible to the human user in the Claude Code terminal UI but is not passed to Claude's context window. The content is written as if it's for the AI agent, but the AI never sees it.
Observed Behavior
After compaction, the human user sees this in their terminal:
SessionStart:compact says: ⚠️ DOGFOODING PROJECT: Bug Handling Protocol...
✅ SessionStart hook system active
🐳 YOLO container (Claude Code CLI in sandbox)
⚠️ WORKFLOW RESTORED AFTER COMPACTION ⚠️
Claude receives only:
SessionStart:compact hook success: Success
Exception: When the daemon is NOT running, the error message DOES reach Claude:
SessionStart hook additional context: HOOKS DAEMON: Not currently running
Error: daemon_startup_failed...
Root Cause (Investigated)
Claude Code's hook response schema defines two distinct fields:
| Field |
Where it goes |
systemMessage |
Terminal display only (human sees it) |
additionalContext |
Claude's context window AND terminal |
All lifecycle event handlers (SessionStart, SessionEnd, PreCompact, Notification) are formatted using systemMessage by HookResult.to_json(), per Claude Code's schema:
# hook_result.py
# SessionStart/SessionEnd/PreCompact/Notification: systemMessage ONLY
return self._format_system_message_response()
The error path (daemon not running) accidentally works because init.sh's emit_hook_error() uses additionalContext — technically a schema violation for SessionStart, but Claude Code handles it gracefully.
Implications
All these handlers currently produce content that only the human sees:
WorkflowStateRestorationHandler — workflow context after compaction
YoloContainerDetectionHandler — container environment info
OptimalConfigCheckerHandler — config validation advice
- Session-start dogfooding advisories
The content is written in a style optimised for AI consumption ("Read ALL files listed above using @ syntax") but the AI never receives it. The human user would need to manually copy-paste it to the agent.
Options
- Leave as-is and rewrite handler output to be human-optimised (humans are the actual audience)
- Use
additionalContext workaround — switch SessionStart responses to use hookSpecificOutput.additionalContext instead of systemMessage. Violates Claude Code's intended schema but works in practice (proven by error path)
- File upstream request to Anthropic — request Claude Code to pass
systemMessage to AI context for lifecycle events
- Hybrid approach — short human-readable summary in
systemMessage, key directives also injected via additionalContext
Research Needed
- Does Claude Code documentation explain the
systemMessage vs additionalContext distinction?
- Have others in the Claude Code community found workarounds?
- Is Option 2 (the
additionalContext workaround) stable/safe to rely on?
- Does the behaviour differ between hook event types (e.g. does UserPromptSubmit support
additionalContext)?
Related Code
src/claude_code_hooks_daemon/core/hook_result.py — to_json() and _format_system_message_response()
.claude/hooks/init.sh — emit_hook_error() function (the working error path using additionalContext)
src/claude_code_hooks_daemon/handlers/session_start/ — all affected handlers
Decision Needed
Once we understand the full picture (especially whether Option 2 is safe), decide:
- Should lifecycle handler content be rewritten for human readability?
- Should we use the
additionalContext workaround to reach Claude?
- Should we file an upstream request to Anthropic?
Problem
Advisory content from SessionStart (and other lifecycle) handlers is visible to the human user in the Claude Code terminal UI but is not passed to Claude's context window. The content is written as if it's for the AI agent, but the AI never sees it.
Observed Behavior
After compaction, the human user sees this in their terminal:
Claude receives only:
Exception: When the daemon is NOT running, the error message DOES reach Claude:
Root Cause (Investigated)
Claude Code's hook response schema defines two distinct fields:
systemMessageadditionalContextAll lifecycle event handlers (SessionStart, SessionEnd, PreCompact, Notification) are formatted using
systemMessagebyHookResult.to_json(), per Claude Code's schema:The error path (daemon not running) accidentally works because
init.sh'semit_hook_error()usesadditionalContext— technically a schema violation for SessionStart, but Claude Code handles it gracefully.Implications
All these handlers currently produce content that only the human sees:
WorkflowStateRestorationHandler— workflow context after compactionYoloContainerDetectionHandler— container environment infoOptimalConfigCheckerHandler— config validation adviceThe content is written in a style optimised for AI consumption ("Read ALL files listed above using @ syntax") but the AI never receives it. The human user would need to manually copy-paste it to the agent.
Options
additionalContextworkaround — switch SessionStart responses to usehookSpecificOutput.additionalContextinstead ofsystemMessage. Violates Claude Code's intended schema but works in practice (proven by error path)systemMessageto AI context for lifecycle eventssystemMessage, key directives also injected viaadditionalContextResearch Needed
systemMessagevsadditionalContextdistinction?additionalContextworkaround) stable/safe to rely on?additionalContext)?Related Code
src/claude_code_hooks_daemon/core/hook_result.py—to_json()and_format_system_message_response().claude/hooks/init.sh—emit_hook_error()function (the working error path usingadditionalContext)src/claude_code_hooks_daemon/handlers/session_start/— all affected handlersDecision Needed
Once we understand the full picture (especially whether Option 2 is safe), decide:
additionalContextworkaround to reach Claude?