◉CLAUDE LABJP
●2.1.296 — Subagents can set their own autoCompactWindow, and an env var pins the model used by workflow agents●AGENTS — Managed Agents gain dynamic workflows in beta: an agent can build a run for work with many pieces (Oct 9)●SONNET 4.5 — Retires on the Claude API on 11/30, 49 days left. Migrate to Sonnet 5.5●Q&A — A write-up shows that handing over only a bug report gets fixes confined to the tests; scoping the request is the point●HAIKU 5.5 — Code written for Haiku 4.5 hits a 400 error on budget_tokens●NEW — Stop making it reread the whole notes file: look up one section by number●2.1.296 — Subagents can set their own autoCompactWindow, and an env var pins the model used by workflow agents●AGENTS — Managed Agents gain dynamic workflows in beta: an agent can build a run for work with many pieces (Oct 9)●SONNET 4.5 — Retires on the Claude API on 11/30, 49 days left. Migrate to Sonnet 5.5●Q&A — A write-up shows that handing over only a bug report gets fixes confined to the tests; scoping the request is the point●HAIKU 5.5 — Code written for Haiku 4.5 hits a 400 error on budget_tokens●NEW — Stop making it reread the whole notes file: look up one section by number
Articles/Claude Code
⟐ Claude Code/2026-08-28Intermediate

Don't let your verification script's full output flow back through a hook

The check was working the whole time. The conversation was what ran out of room. Measured side by side: 58 bytes from one scan of 833 files, 42KB from another. Which paths actually reach the model, and how to enforce a budget on them.

Claude Code260hooks20automation109context design4quality gates

✦ Premium Article

The check ran correctly to the very end. The conversation was the thing that broke.

I had added a hook that ran a static check after every edit. Working alone as an indie developer across several repositories, I have found that checks tied to the edit itself hold up far better than checks I have to remember to run. The check worked exactly as intended and caught the violations it was supposed to catch. But somewhere after a few dozen edits, responses got noticeably sluggish, and eventually long prompts stopped going through at all.

I spent a while staring at the check script looking for the problem. Wasteful loops. Slow regular expressions. But it was never about processing time. The strings the check was returning had been piling up inside the conversation, one edit at a time.

Hook timeouts are a separate topic, covered in what to look at when a hook dies with command timed out. This piece is about the other axis: not duration, but volume.

What lands in context is what you returned, not what you scanned

Here are numbers I measured on my own machine. Against the same repository (833 MDX files), I ran several checks with different personalities and recorded the bytes and lines each one wrote to standard output.

ProcessScope scannedOutput bytesLinesApprox. tokens
Frontmatter integrity check (pass)833 files58 B1~26
Verbatim-duplication scan (pass)833 files109 B1~50
Redirect integrity check (pass)whole repo126 B1~57
Single-file check (violations found)2 files893 B18~406
Enumerating grep833 files42,280 B398~19,218

Token figures are rough, based on roughly 2.2 bytes per token for mixed Japanese and English text. Your tokenizer will give different numbers.

The gap between the top three rows and the last one is the part worth sitting with. The same 833 files, scanned by both — one returns 58 bytes, the other 42,280. Roughly 728 times more. The size of what you scan does not determine the size of what you return. The only thing that determines it is what the author decided to print.

Until I built that table, I had been assuming that heavier checks produce heavier output. The opposite tends to be true. A well-designed full-repository check returns one line when everything passes. A hastily written grep -rn prints every matching line it finds. The second one is far cheaper to run and three orders of magnitude more expensive to keep.

Does that output actually reach the conversation?

Here I have to walk something back. For a long time I assumed that anything a hook printed went into the conversation. Re-reading the official hooks reference after I had finished the first draft of this piece, I found that my mental model had been too coarse.

The path decides.

How you return itDoes Claude see it?Notes
stdout on exit 0 (most events)NoWritten to the debug log; it does not appear in the transcript
stdout on exit 0 (SessionStart, UserPromptSubmit, UserPromptExpansion, PostModelSwitch)YesPlain text is added to context as-is
stderr on exit 0NoDebug log only, unless you enable debug logging yourself
stderr on exit 2 (e.g. PostToolUse)YesThe tool has already run; the text is shown to Claude
hookSpecificOutput.additionalContextYesInserted into the conversation as a system reminder

The paths that do reach Claude have a ceiling. additionalContext, systemMessage, and plain stdout are all capped at 10,000 characters, with anything beyond that written to a file and replaced by a preview plus a path. So my original line about one misplaced cat dumping 5.7MB into the conversation was not accurate. A single blowout never gets that far.

That does not make it safe. What actually hurts is not one large payload but a small one, on a path that reaches the model, repeated forever. A hook that fires on every edit and returns 400 bytes costs 120,000 bytes across 300 edits, without ever touching the per-call limit once. That is the version I lived through.

Exit status has a similar trap. exit 2 is what enforces a policy, and exit 1 without valid JSON on stdout is treated as a non-blocking error — the action simply proceeds. Follow the Unix convention and you get a check that finds violations and stops nothing.

Output volume and exit status are independent axes, and all four combinations exist.

Short outputLong output
exit 0A passing check. Make this the defaultSuspicious. It passed and, on a reaching path, still spent your context
exit 2A failure you can state in one line. IdealFine within budget. Over budget, summarize and offload

The quadrant people miss is the top right: passing, and long. Nothing failed, so nobody investigates. No error appears anywhere. And it quietly removes a slice of the context window on every run. I lost real time reading failure logs for exactly this reason. The culprit had never failed once.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦You will see why one scan of 833 files returns 58 bytes and another returns 42,280, and be able to estimate output before wiring a check into a hook
✦You will know which of stdout on exit 0, stderr on exit 2, and additionalContext actually reaches Claude
✦You will be able to write a budget wrapper that avoids the head -c mojibake and the non-blocking exit 1 trap
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $15 for lifetime access
View Membership →

Related Articles

⟐ Claude Code2026-09-02
Two hooks that record every model switch and stop the ones you never agreed to
Build a PostModelSwitch hook that logs every model change and a PreModelSwitch hook that blocks unapproved switches during unattended runs. Complete scripts, measured timings, and the reason the two jobs must stay separate.
⟐ Claude Code2026-10-05
I Wrote "I'll Do the Commits" in CLAUDE.md. The Next Morning git log Had One More Line.
When Claude Code commits despite being told not to: undo with git reset --soft, then stop it physically with permissions.deny plus a PreToolUse hook. Covers the git -c form that deny rules let through, and how to test the hook without starting Claude Code.
⟐ Claude Code2026-08-31
Half of My Scheduled Runs Vanished Without a Single Error
A batch job set to run twice a day was only firing once. No errors, no failure alerts. Here is how to expand your own schedule, count expected runs, and reconcile them against execution records to catch silent misses.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links