CLAUDE LABJP
2.1.273 — A round of connection work landed together: five opt-in headers for LLM gateways, and a notice when Claude Code stops trying to reconnect an MCP server09/29 — The date beside claude-sonnet-4-5 is 12 days out, but it is an earliest-possible estimate. The model is still Active, and public retirements get at least 60 days noticeMCP — People keep asking to reconnect a dropped server without ending the session. The disconnect is now announced, but reattaching is still something you do by handNEW — A scheduled task ran some days and not others. The cause was that only one folder had been bound to itWINDOWS — When Cowork fails on its very first task, check developer mode and the setup state before going looking for a causeHANDOFF — Before a long draft gets too heavy for one chat, decide on the three things the summary must carry into the next one2.1.273 — A round of connection work landed together: five opt-in headers for LLM gateways, and a notice when Claude Code stops trying to reconnect an MCP server09/29 — The date beside claude-sonnet-4-5 is 12 days out, but it is an earliest-possible estimate. The model is still Active, and public retirements get at least 60 days noticeMCP — People keep asking to reconnect a dropped server without ending the session. The disconnect is now announced, but reattaching is still something you do by handNEW — A scheduled task ran some days and not others. The cause was that only one folder had been bound to itWINDOWS — When Cowork fails on its very first task, check developer mode and the setup state before going looking for a causeHANDOFF — Before a long draft gets too heavy for one chat, decide on the three things the summary must carry into the next one
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 Code253hooks19automation110context 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-07-10
Carrying Decisions Across Compaction with PreCompact and SessionEnd Hooks
Auto-compaction does not delete your conversation. It deletes the reasons behind it. Here is a working PreCompact / SessionEnd / SessionStart hook pipeline that rescues decisions to disk and hands them to the next session, with real code and measurements.
Claude Code2026-07-01
My Claude Code Hooks Stopped Firing After an Update — the Hyphenated Matcher Exact-Match Change in v2.1.195
In Claude Code v2.1.195, hook matchers containing a hyphen switched from partial match to exact match, silently disabling an existing PreToolUse hook. Here is how I isolated the cause and how to write matchers that won't break.
📚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