SessionStart hook output past the inline preview cap is silently invisible to the model

Resolved 💬 3 comments Opened May 3, 2026 by alexmensch Closed May 6, 2026

Summary

When a SessionStart hook emits more than the inline preview cap (~2KB observed), Claude Code truncates the system-reminder shown to the model to a short preview and persists the full output to a file. The model is shown the path but is not directed (or auto-caused) to read it — so any content past the cap is silently invisible to the session, even though the hook author intended that content to be loaded into context.

This is a footgun specifically for SessionStart hooks, which are the natural place to inject durable per-project context (workflow rules, persistent memories, project conventions). When the hook author's payload exceeds the cap, the model behaves as if the hook had never run for the substantive content — and the failure is silent on both sides (hook author thinks "I loaded memories"; model sees only headers).

Symptoms

A SessionStart hook (e.g. bd prime from the beads issue tracker, which loads project memories) runs successfully and writes ~10–20 KB. The visible system-reminder is:

SessionStart:clear hook success: <persisted-output>
Output too large (19.9KB). Full output saved to: <path>

Preview (first 2KB):
# ...first ~50 lines...

If the substantive payload (memories, rules, etc.) is rendered past line ~50, it never reaches the model's context. The model proceeds without it and silently violates conventions it would have honoured if the hook output had fit inline.

Real example currently live in the wild: gastownhall/beads#3677. bd prime emits ~20KB of workflow rules followed by stored memories at line ~108. With the current 2KB preview cap, the memories never arrive — the model only sees the workflow header. A user noticed the bug after the model violated a stored \"bump semver on every PR\" rule that was sitting in the persisted file the model never read.

Root cause

Two design choices interact badly:

  1. SessionStart hook output above the inline cap is persisted to disk, not inlined into the system-reminder. (Reasonable for unbounded output — build logs etc.)
  2. The system-reminder telling the model about the persisted file is descriptive (\"Full output saved to: …\"), not imperative. The model is given the path but receives no instruction to read it as part of session bootstrap.

Combined: silent context loss for SessionStart hooks specifically, which is the moment the hook author had the highest expectation that emitted content would land in context.

This is asymmetric with how other hook channels behave — PreToolUse decisions, for instance, are surfaced fully and acted on. SessionStart is the channel where truncation hurts most and is least expected by hook authors.

Reproduction

  1. Configure a SessionStart hook that prints >2KB:

``json
{
\"hooks\": {
\"SessionStart\": [{
\"hooks\": [{ \"type\": \"command\", \"command\": \"yes 'memory line' | head -200\" }],
\"matcher\": \"\"
}]
}
}
``

  1. Open a new session.
  2. Ask the model to recall content from past line ~50 of the hook output. It can't — the path was shown but never read.
  3. Manually instruct the model to Read the persisted file. It immediately recovers full context, confirming the content was emitted but never reached the prompt.

Suggested resolutions (any one closes the gap)

In order of robustness:

  1. Auto-read persisted SessionStart hook output. When a SessionStart hook's full output is persisted, the harness should Read the file into the model's context as part of session bootstrap. Same effect as if the hook had fit inline. Most reliable; matches the user's expectation that \"SessionStart loads context.\"
  2. Raise the inline cap for SessionStart specifically. SessionStart hook output is author-controlled and bounded in practice (typically <50 KB). A 32–64 KB cap for SessionStart would absorb realistic loads without truncation. Other hook types could keep tighter caps where unbounded output is more likely.
  3. Make the system-reminder a directive. Change the truncation message from descriptive (\"Full output saved to: …\") to imperative (\"Read this path before continuing — it contains content the SessionStart hook intended to load into context\"). Cheapest fix, least reliable — depends on the model picking up the hint.
  4. Hook-author opt-in for \"always inline.\" Add a hook config flag (e.g. \"inlineFullOutput\": true) so SessionStart hooks that load context can opt out of truncation regardless of size. Useful as a fallback for hook authors who know their output should always reach the model.

Current workaround

User-global ~/.claude/CLAUDE.md directive telling the model to Read the persisted-output file whenever the truncation system-reminder appears. Works in practice, but every Claude Code user has to discover and apply this independently. The companion beads-side mitigation is in gastownhall/beads#3677, which proposes adding a \"if you are reading a truncated preview, Read the full file\" directive line at the top of bd prime's output.

Both workarounds put the burden on hook authors and end users. The structural fix belongs in the harness — any of resolutions 1–4 above would prevent the silent failure mode.

View original on GitHub ↗

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