<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Token-Budget on ICE-ICE-BEAR-BLOG</title><link>https://ice-ice-bear.github.io/tags/token-budget/</link><description>Recent content in Token-Budget on ICE-ICE-BEAR-BLOG</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Mon, 06 Apr 2026 00:00:00 +0900</lastBuildDate><atom:link href="https://ice-ice-bear.github.io/tags/token-budget/index.xml" rel="self" type="application/rss+xml"/><item><title>Claude Code Harness Anatomy #4 — Runtime Hooks: 26+ Events and CLAUDE.md 6-Stage Discovery</title><link>https://ice-ice-bear.github.io/posts/2026-04-06-harness-anatomy-4/</link><pubDate>Mon, 06 Apr 2026 00:00:00 +0900</pubDate><guid>https://ice-ice-bear.github.io/posts/2026-04-06-harness-anatomy-4/</guid><description>&lt;img src="https://ice-ice-bear.github.io/" alt="Featured image of post Claude Code Harness Anatomy #4 — Runtime Hooks: 26+ Events and CLAUDE.md 6-Stage Discovery" /&gt;&lt;h2 id="overview"&gt;Overview
&lt;/h2&gt;&lt;p&gt;In Claude Code, the word &amp;ldquo;hook&amp;rdquo; refers to &lt;strong&gt;two completely different systems&lt;/strong&gt;. Runtime hooks (&lt;code&gt;toolHooks.ts&lt;/code&gt; + &lt;code&gt;utils/hooks.ts&lt;/code&gt;) are a security/extension pipeline that executes shell scripts before and after tool execution, while React hooks (&lt;code&gt;hooks/*.ts&lt;/code&gt;, 85+) are state management code for the terminal UI. Missing this distinction leads to a 85x overestimation of the Rust reimplementation scope. This post analyzes the PreToolUse/PostToolUse pipeline of runtime hooks, the security invariant of &lt;code&gt;resolveHookPermissionDecision()&lt;/code&gt;, the 9-category classification of 85 React hooks, and CLAUDE.md&amp;rsquo;s 6-stage discovery with token budget management.&lt;/p&gt;
&lt;h2 id="1-runtime-hooks-vs-react-hooks--the-key-distinction"&gt;1. Runtime Hooks vs React Hooks &amp;ndash; The Key Distinction
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Dimension&lt;/th&gt;
 &lt;th&gt;Runtime Hooks (toolHooks.ts + utils/hooks.ts)&lt;/th&gt;
 &lt;th&gt;React Hooks (hooks/*.ts)&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Executor&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;child_process.spawn()&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;React render cycle&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Configuration&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;settings.json &lt;code&gt;hooks&lt;/code&gt; field, shell commands&lt;/td&gt;
 &lt;td&gt;Source code &lt;code&gt;import&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Execution timing&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Before/after tool use, session start, etc. (26+ events)&lt;/td&gt;
 &lt;td&gt;Component mount/update&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;User-defined&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Yes — users register shell scripts&lt;/td&gt;
 &lt;td&gt;No — internal code&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Result format&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;JSON stdout (allow/deny/ask/rewrite)&lt;/td&gt;
 &lt;td&gt;React state changes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Rust reimplementation&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Required — core of tool execution pipeline&lt;/td&gt;
 &lt;td&gt;Not needed — TUI only&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="2-pretooluse-pipeline--7-yield-variants"&gt;2. PreToolUse Pipeline &amp;ndash; 7 Yield Variants
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;runPreToolUseHooks()&lt;/code&gt; (toolHooks.ts:435-650) is designed as an AsyncGenerator. Called before tool execution, it emits the following yield types:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;message&lt;/code&gt;&lt;/strong&gt;: Progress messages (hook start/error/cancel)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;hookPermissionResult&lt;/code&gt;&lt;/strong&gt;: allow/deny/ask decision&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;hookUpdatedInput&lt;/code&gt;&lt;/strong&gt;: Input rewrite (changes input without a permission decision)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;preventContinuation&lt;/code&gt;&lt;/strong&gt;: Execution halt flag&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;stopReason&lt;/code&gt;&lt;/strong&gt;: Halt reason string&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;additionalContext&lt;/code&gt;&lt;/strong&gt;: Additional context to pass to the model&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;stop&lt;/code&gt;&lt;/strong&gt;: Immediate halt&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Why AsyncGenerator?&lt;/strong&gt; Hooks execute sequentially, and each hook&amp;rsquo;s result affects subsequent processing. Promise chaining returns only the final result, and event emitters lack type safety. AsyncGenerator is the only pattern that lets the caller consume each result and halt mid-stream.&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TD
 subgraph "PreToolUse Pipeline"
 A["toolExecution.ts&amp;lt;br/&amp;gt;Tool call begins"]
 B["runPreToolUseHooks()&amp;lt;br/&amp;gt;toolHooks.ts:435"]
 C["getMatchingHooks()&amp;lt;br/&amp;gt;utils/hooks.ts:1603"]
 D["settings.json hooks&amp;lt;br/&amp;gt;event + pattern matching"]
 E["spawn() shell command&amp;lt;br/&amp;gt;stdin: JSON, stdout: result"]
 F["HookResult parsing&amp;lt;br/&amp;gt;allow / deny / ask / rewrite"]
 end

 subgraph "Permission Resolution"
 G["resolveHookPermission&amp;lt;br/&amp;gt;Decision()&amp;lt;br/&amp;gt;toolHooks.ts:332"]
 H{"Hook result?"}
 I["allow: checkRule&amp;lt;br/&amp;gt;BasedPermissions()&amp;lt;br/&amp;gt;rules override hooks"]
 J["deny: immediate rejection"]
 K["ask: canUseTool()&amp;lt;br/&amp;gt;user prompt"]
 end

 subgraph "Tool Execution"
 L["tool.call()"]
 end

 subgraph "PostToolUse"
 M["runPostToolUseHooks()&amp;lt;br/&amp;gt;result transform / block"]
 end

 A --&gt; B --&gt; C --&gt; D --&gt; E --&gt; F --&gt; G --&gt; H
 H --&gt;|"allow"| I
 H --&gt;|"deny"| J
 H --&gt;|"ask"| K
 I --&gt;|"Rules pass"| L
 L --&gt; M&lt;/pre&gt;&lt;h3 id="resolvehookpermissiondecision--allow--bypass"&gt;resolveHookPermissionDecision &amp;ndash; allow != bypass
&lt;/h3&gt;&lt;p&gt;The core invariant of &lt;code&gt;resolveHookPermissionDecision()&lt;/code&gt; (toolHooks.ts:332-433): &lt;strong&gt;a hook&amp;rsquo;s &lt;code&gt;allow&lt;/code&gt; does not bypass settings.json deny/ask rules&lt;/strong&gt; (toolHooks.ts:325-327).&lt;/p&gt;
&lt;p&gt;The processing logic has 3 stages:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stage 1 &amp;ndash; allow handling&lt;/strong&gt; (toolHooks.ts:347-406):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hookResult.behavior === &amp;#39;allow&amp;#39;:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; Call checkRuleBasedPermissions()
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; null -&amp;gt; no rules, hook allow passes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; deny -&amp;gt; rule overrides hook (security first!)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -&amp;gt; ask -&amp;gt; user prompt required
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Why doesn&amp;rsquo;t allow bypass?&lt;/strong&gt; This is a deliberate security decision. If an external shell script returning &lt;code&gt;{&amp;quot;decision&amp;quot;:&amp;quot;allow&amp;quot;}&lt;/code&gt; could override &lt;code&gt;settings.json&lt;/code&gt; deny rules, a malicious hook could circumvent security policies. &lt;strong&gt;Rules always take precedence over hooks.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stage 2 &amp;ndash; deny&lt;/strong&gt; (toolHooks.ts:408-411): Immediate rejection, no further checks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stage 3 &amp;ndash; ask/none&lt;/strong&gt; (toolHooks.ts:413-432): Calls &lt;code&gt;canUseTool()&lt;/code&gt; for user prompt.&lt;/p&gt;
&lt;h3 id="26-event-types"&gt;26+ Event Types
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;getMatchingHooks()&lt;/code&gt; (utils/hooks.ts:1603-1682) handles hook matching:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tool events&lt;/strong&gt;: PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Session events&lt;/strong&gt;: SessionStart, SessionEnd, Setup&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agent events&lt;/strong&gt;: SubagentStart, SubagentStop, TeammateIdle&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Task events&lt;/strong&gt;: TaskCreated, TaskCompleted&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;System events&lt;/strong&gt;: Notification, ConfigChange, FileChanged, InstructionsLoaded&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compact events&lt;/strong&gt;: PreCompact, PostCompact&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Input events&lt;/strong&gt;: UserPromptSubmit, Elicitation, ElicitationResult&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stop events&lt;/strong&gt;: Stop, StopFailure&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Matched hooks execute &lt;strong&gt;sequentially&lt;/strong&gt;, and if one denies, subsequent hooks are not executed.&lt;/p&gt;
&lt;h2 id="3-85-react-hooks--9-category-classification"&gt;3. 85 React Hooks &amp;ndash; 9 Category Classification
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;mindmap
 root(("TS Hook System"))
 Runtime Hooks
 toolHooks.ts 651 lines
 PreToolUse
 PostToolUse
 PostToolUseFailure
 utils/hooks.ts ~5000 lines
 26+ event types
 Shell spawn
 Async protocol
 React Hooks 85+
 Permission 3
 useCanUseTool
 PermissionContext
 UI Input 11
 useTextInput
 useVimInput
 useTypeahead
 UI Display 11
 useVirtualScroll
 useDiffData
 State/Config 12
 useSettings
 useSessionBackgrounding
 Integration/Remote 12
 useRemoteSession
 useReplBridge
 Features 20
 useVoice
 useSwarm
 useTasks
 Notifications 16
 notifs/ directory
 Tools/Keybindings 5
 useMergedTools
 Additional 5+
 fileSuggestions
 useManagePlugins&lt;/pre&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Category&lt;/th&gt;
 &lt;th&gt;Count&lt;/th&gt;
 &lt;th&gt;Rust Reimpl&lt;/th&gt;
 &lt;th&gt;Representative Hook&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Permission&lt;/td&gt;
 &lt;td&gt;3&lt;/td&gt;
 &lt;td&gt;Partial (bridge)&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;useCanUseTool&lt;/code&gt; (203 lines)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;UI Input&lt;/td&gt;
 &lt;td&gt;11&lt;/td&gt;
 &lt;td&gt;Not needed&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;useTextInput&lt;/code&gt; (529 lines), &lt;code&gt;useVimInput&lt;/code&gt; (316 lines)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;UI Display&lt;/td&gt;
 &lt;td&gt;11&lt;/td&gt;
 &lt;td&gt;Not needed&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;useVirtualScroll&lt;/code&gt; (721 lines)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;State/Config&lt;/td&gt;
 &lt;td&gt;12&lt;/td&gt;
 &lt;td&gt;Not needed&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;useSessionBackgrounding&lt;/code&gt; (158 lines)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Integration/Remote&lt;/td&gt;
 &lt;td&gt;12&lt;/td&gt;
 &lt;td&gt;Not needed&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;useRemoteSession&lt;/code&gt; (605 lines)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Features/Notifications&lt;/td&gt;
 &lt;td&gt;20&lt;/td&gt;
 &lt;td&gt;Not needed&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;useVoice&lt;/code&gt; (1,144 lines)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Notifications/Banners&lt;/td&gt;
 &lt;td&gt;16&lt;/td&gt;
 &lt;td&gt;Not needed&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;notifs/&lt;/code&gt; directory&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Tools/Keybindings&lt;/td&gt;
 &lt;td&gt;5&lt;/td&gt;
 &lt;td&gt;Not needed&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;useMergedTools&lt;/code&gt; (44 lines)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Additional&lt;/td&gt;
 &lt;td&gt;5+&lt;/td&gt;
 &lt;td&gt;Not needed&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;fileSuggestions&lt;/code&gt; (811 lines)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Key takeaway&lt;/strong&gt;: What Rust needs to reimplement is only the runtime pipeline of &lt;code&gt;toolHooks.ts&lt;/code&gt; (651 lines) + &lt;code&gt;utils/hooks.ts&lt;/code&gt; (~5,000 lines). The 85 React hooks totaling 15,000+ lines are out of scope.&lt;/p&gt;
&lt;h2 id="4-claudemd-6-stage-discovery"&gt;4. CLAUDE.md 6-Stage Discovery
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;getMemoryFiles()&lt;/code&gt; in &lt;code&gt;claudemd.ts&lt;/code&gt; (1,479 lines, L790-1074) loads CLAUDE.md through a 6-stage hierarchy:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Stage&lt;/th&gt;
 &lt;th&gt;Source&lt;/th&gt;
 &lt;th&gt;Path Example&lt;/th&gt;
 &lt;th&gt;Priority&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;1. Managed&lt;/td&gt;
 &lt;td&gt;Org policy&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;/etc/claude-code/CLAUDE.md&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Lowest&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2. User&lt;/td&gt;
 &lt;td&gt;Personal habits&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt;, &lt;code&gt;~/.claude/rules/*.md&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3. Project&lt;/td&gt;
 &lt;td&gt;Project rules&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; and &lt;code&gt;.claude/rules/*.md&lt;/code&gt; from cwd to root&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;4. Local&lt;/td&gt;
 &lt;td&gt;Local overrides&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;CLAUDE.local.md&lt;/code&gt; (gitignored)&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;5. AutoMem&lt;/td&gt;
 &lt;td&gt;Auto memory&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;MEMORY.md&lt;/code&gt; entrypoint&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;6. TeamMem&lt;/td&gt;
 &lt;td&gt;Team memory&lt;/td&gt;
 &lt;td&gt;Cross-org sync&lt;/td&gt;
 &lt;td&gt;Highest&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Why this order?&lt;/strong&gt; The file comment (L9) states explicitly: &amp;ldquo;Files are loaded in reverse order of priority.&amp;rdquo; LLMs pay more attention to later parts of the prompt, so the most specific instructions (Local &amp;gt; Project &amp;gt; User &amp;gt; Managed) are &lt;strong&gt;placed last&lt;/strong&gt;. This is not CSS specificity — it&amp;rsquo;s a design that leverages &lt;strong&gt;LLM attention bias&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id="upward-directory-traversal-and-deduplication"&gt;Upward Directory Traversal and Deduplication
&lt;/h3&gt;&lt;p&gt;Starting from &lt;code&gt;originalCwd&lt;/code&gt;, it walks up to the filesystem root, then calls &lt;code&gt;dirs.reverse()&lt;/code&gt; to process &lt;strong&gt;from root downward&lt;/strong&gt; (L851-857). In monorepos, the parent &lt;code&gt;CLAUDE.md&lt;/code&gt; loads first and the child project&amp;rsquo;s &lt;code&gt;CLAUDE.md&lt;/code&gt; layers on top.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Worktree deduplication&lt;/strong&gt; (L868-884): When a git worktree is nested inside the main repo, an &lt;code&gt;isNestedWorktree&lt;/code&gt; check prevents the same &lt;code&gt;CLAUDE.md&lt;/code&gt; from being loaded twice.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;@include directive&lt;/strong&gt; (L451-535): Lexes markdown tokens to ignore &lt;code&gt;@path&lt;/code&gt; inside code blocks, recursively resolving only &lt;code&gt;@path&lt;/code&gt; in text nodes. Maximum depth of 5.&lt;/p&gt;
&lt;h2 id="5-systemuser-context-separation--dual-memoize-cache"&gt;5. System/User Context Separation &amp;ndash; dual-memoize Cache
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;context.ts&lt;/code&gt; (189 lines) separates the system prompt into &lt;strong&gt;two independent contexts&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;getSystemContext()&lt;/code&gt;&lt;/strong&gt; (L116): Git state, cache breaker&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;getUserContext()&lt;/code&gt;&lt;/strong&gt; (L155): CLAUDE.md merged string, current date&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Why split into two?&lt;/strong&gt; Because of the Anthropic API&amp;rsquo;s prompt caching strategy. Git state (session-fixed) and CLAUDE.md (invalidated only on file changes) have different cache lifetimes, so &lt;code&gt;cache_control&lt;/code&gt; must be applied differently. Both functions are wrapped in &lt;code&gt;memoize&lt;/code&gt; and execute only once per session.&lt;/p&gt;
&lt;h3 id="3-cache-invalidation-paths"&gt;3 Cache Invalidation Paths
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;setSystemPromptInjection()&lt;/code&gt; (context.ts:29): Clears both caches&lt;/li&gt;
&lt;li&gt;&lt;code&gt;clearMemoryFileCaches()&lt;/code&gt; (claudemd.ts:1119): Clears memory files only&lt;/li&gt;
&lt;li&gt;&lt;code&gt;resetGetMemoryFilesCache()&lt;/code&gt; (claudemd.ts:1124): Clears memory files + fires &lt;code&gt;InstructionsLoaded&lt;/code&gt; hook&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This separation distinguishes between worktree switches (no reload needed) and actual reloads (after compaction).&lt;/p&gt;
&lt;h2 id="6-token-budget--response-continuation-decisions"&gt;6. Token Budget &amp;ndash; Response Continuation Decisions
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;checkTokenBudget()&lt;/code&gt; in &lt;code&gt;tokenBudget.ts&lt;/code&gt; (93 lines) controls &lt;strong&gt;whether to continue responding, not prompt size&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;COMPLETION_THRESHOLD = 0.9 -- continue if below 90%
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DIMINISHING_THRESHOLD = 500 -- 3+ consecutive turns, &amp;lt;500 tokens each -&amp;gt; diminishing returns
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;if (!isDiminishing &amp;amp;&amp;amp; turnTokens &amp;lt; budget * 0.9) -&amp;gt; continue
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;if (isDiminishing || continuationCount &amp;gt; 0) -&amp;gt; stop with event
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;else -&amp;gt; stop without event
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Why 0.9?&lt;/strong&gt; Models tend to start summarizing near the budget limit. Stopping at 90% prevents &amp;ldquo;wrapping up&amp;rdquo; summaries and keeps the work going. The &lt;code&gt;nudgeMessage&lt;/code&gt; explicitly instructs &amp;ldquo;do not summarize.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Diminishing returns detection prevents the model from falling into repetitive patterns. &lt;strong&gt;Sub-agents stop immediately&lt;/strong&gt; (L51) — they don&amp;rsquo;t have their own budgets.&lt;/p&gt;
&lt;h2 id="rust-comparison"&gt;Rust Comparison
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Aspect&lt;/th&gt;
 &lt;th&gt;TS&lt;/th&gt;
 &lt;th&gt;Rust&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Hook event types&lt;/td&gt;
 &lt;td&gt;26+&lt;/td&gt;
 &lt;td&gt;PreToolUse, PostToolUse (2 only)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Hook execution&lt;/td&gt;
 &lt;td&gt;Async AsyncGenerator&lt;/td&gt;
 &lt;td&gt;Synchronous &lt;code&gt;Command::output()&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Hook results&lt;/td&gt;
 &lt;td&gt;7 yield variants + JSON&lt;/td&gt;
 &lt;td&gt;Allow/Deny/Warn (3 via exit code)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Input modification&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;hookUpdatedInput&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Not possible&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;allow != bypass&lt;/td&gt;
 &lt;td&gt;Guaranteed&lt;/td&gt;
 &lt;td&gt;Not implemented (security vulnerability)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;CLAUDE.md&lt;/td&gt;
 &lt;td&gt;6-stage discovery&lt;/td&gt;
 &lt;td&gt;4 candidates per dir&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;@include&lt;/td&gt;
 &lt;td&gt;Recursive, depth 5&lt;/td&gt;
 &lt;td&gt;Not supported&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Token budget&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;checkTokenBudget()&lt;/code&gt; with 3 decisions&lt;/td&gt;
 &lt;td&gt;None&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Prompt cache&lt;/td&gt;
 &lt;td&gt;memoize + 3 invalidation paths&lt;/td&gt;
 &lt;td&gt;Rebuilt every time&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="insights"&gt;Insights
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The dual meaning of &amp;ldquo;hook&amp;rdquo; is the biggest source of architectural confusion&lt;/strong&gt; &amp;ndash; The 85 React hooks are not in scope for Rust reimplementation. Only the runtime hooks (~5,600 lines) are porting targets. However, this runtime engine includes 26 event types, an async protocol (&lt;code&gt;{&amp;quot;async&amp;quot;:true}&lt;/code&gt; background switching), and prompt requests (bidirectional stdin/stdout). Precisely scoping the meaning of &amp;ldquo;hooks&amp;rdquo; is the starting point for accurate estimation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CLAUDE.md&amp;rsquo;s &amp;ldquo;last is strongest&amp;rdquo; pattern is deliberate exploitation of LLM attention bias&lt;/strong&gt; &amp;ndash; In the 6-stage hierarchical loading (Managed -&amp;gt; User -&amp;gt; Project -&amp;gt; Local -&amp;gt; AutoMem -&amp;gt; TeamMem), the most specific instructions are placed at the end of the prompt for maximum influence. This design emerges at the intersection of &lt;strong&gt;API prompt cache hit-rate optimization + LLM behavioral characteristics&lt;/strong&gt;, not from architectural tidiness.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The &amp;ldquo;allow != bypass&amp;rdquo; invariant in &lt;code&gt;resolveHookPermissionDecision()&lt;/code&gt; is the security cornerstone&lt;/strong&gt; &amp;ndash; The current Rust hooks.rs judges allow/deny solely by exit code. Without implementing JSON result parsing and the subsequent &lt;code&gt;checkRuleBasedPermissions&lt;/code&gt; check, a malicious hook could bypass deny rules — a security vulnerability. Clearly delineating the boundary between automation convenience and security policy is the fundamental challenge of the hook system.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;Next post: &lt;a class="link" href="https://ice-ice-bear.github.io/posts/2026-04-06-harness-anatomy-5/" &gt;#5 &amp;ndash; MCP Services and the Plugin-Skill Extension Ecosystem&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description></item></channel></rss>