Skip to content

SessionStart/lifecycle handler content visible to human only, not to Claude AI #23

@edmondscommerce

Description

@edmondscommerce

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

  1. Leave as-is and rewrite handler output to be human-optimised (humans are the actual audience)
  2. 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)
  3. File upstream request to Anthropic — request Claude Code to pass systemMessage to AI context for lifecycle events
  4. 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.pyto_json() and _format_system_message_response()
  • .claude/hooks/init.shemit_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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions