Moving Past Static Rules: Why Claude Code Went In-Process
For the past year, agent customization has largely been trapped between two extremes: declarative text steering and decoupled out-of-process protocols. We crafted instructions in CLAUDE.md or .cursorrules hoping the model would internalize our constraints, or we wired external Model Context Protocol (MCP) servers across stdio pipes to provide capabilities. In practice, both approaches hit a hard ceiling when building deterministic developer workflows. Declarative steering is probabilistic by definition—a system prompt cannot guarantee that a destructive shell command will be vetoed before execution—and external protocols introduce structural latency that makes real-time terminal manipulation untenable.

When you operate a terminal coding agent, every millisecond of IPC overhead matters. Passing serialized JSON-RPC envelopes back and forth over child-process stdio works for data retrieval against a remote database or fetching documentation. But the moment you attempt to intercept dynamic token streams, validate generated shell payloads against local security baselines, or update interactive terminal UI panes, stdio IPC becomes an architectural bottleneck. Spawning subprocesses or bouncing messages across serialization boundaries introduces frame stutter, terminal cursor desynchronization, and unacceptable latency into the core inference loop.
Claude Code’s introduction of mods addresses this architectural debt by shifting extensibility directly into the host process. Instead of treating the CLI as a static harness that polls external subprocesses, mods run compiled TypeScript within the agent’s own Node.js runtime. This gives extensions direct access to the agent's internal lifecycle registry, memory buffers, and render trees.
I see this not as an incremental feature, but as Anthropic adopting the extensibility philosophy of Neovim rather than the sandboxed rigidity of modern web IDEs. In Neovim, Lua scripts run in-process, patching core editor buffers and event loops with zero serialization overhead. Claude Code brings this exact paradigm to terminal agents.
Anthropic validated this architectural pivot internally by dogfooding the mod framework across its own core features. Key capabilities that previously seemed hardcoded into the binary were decoupled into internal mods:
- The visual side-by-side git diff viewer (
diff) - The instruction parser and context injector for
AGENTS.mdspecifications (agents-md) - The telemetry dispatchers and default security guardrails (
telemetryandsec-default)
By rebuilding core features as first-party mods, Anthropic proved that the agent host can remain lean while critical behavioral logic, terminal views, and execution guards are loaded dynamically into the same runtime. This gives teams deterministic, sub-millisecond programmatic control over the agent loop without relying on the model to follow prompt-based suggestions.
The Hook Lifecycle: Intercepting Prompts, Tools, and Terminal UI
The power of in-process modding lies in its event-driven lifecycle. Rather than reacting after actions take place, mods act as synchronous and asynchronous middleware within the Node.js execution loop. Packaged inside distributable plugins, a mod exposes a manifest—typically structured through configuration declarations like hooks/hooks.json—that binds custom TypeScript handlers directly to the agent's internal event registry.
When a session initializes, the runtime loads these modules into memory. The lifecycle exposes precise intervention points across three distinct domains: session management, the prompt-inference loop, and tool execution.
sequenceDiagram
autonumber
actor User as Developer Terminal
participant Host as Claude Code Runtime
participant Mod as In-Process Mod Hook
participant LLM as Anthropic API
participant Shell as Local Host Shell
User->>Host: Enter command / prompt
Host->>Mod: onSessionStart / beforePrompt
Note over Mod: Intercept context, strip secrets,<br/>mutate system prompt in-process
Mod-->>Host: Mutated prompt payload
Host->>LLM: Dispatch inference request
LLM-->>Host: Stream response & tool invocation
Host->>Mod: beforeToolCall (bash payload)
alt Policy Validation Passed
Mod-->>Host: Approve / rewrite payload
Host->>Shell: Execute sanitized command
Shell-->>Host: Return stdout/stderr
else Policy Violation
Mod-->>Host: Halt execution & raise error
end
Host->>Mod: afterResponse
Note over Mod: Update custom Ink/TUI panes<br/>and layout state in-process
Mod-->>Host: Virtual DOM / buffer update
Host-->>User: Redraw terminal frame
During initialization, onSessionStart fires, allowing mods to configure local state, verify environment integrity, or establish persistent connections before any user interaction occurs. When a developer submits a prompt, execution enters the message pipeline via beforePrompt. Because the mod runs inside the host Node.js process, it can synchronously inspect the assembled context, query local project state, strip sensitive keys, or inject deterministic rules directly into the system prompt payload before network dispatch to the Anthropic API.
Once the model generates a response specifying a tool call, beforeToolCall intercepts the execution payload. In standard agent architectures, checking command safety often depends on sending the generated command back to an LLM or waiting for human confirmation. With in-process hooks, deterministic code inspection happens instantly. A security mod can parse a bash AST, verify that destructive flags are absent, or dynamically rewrite file paths before the command ever touches a shell subprocess. If a command violates policy, the mod can reject the call programmatically or adjust the permission request, preventing unvetted tool execution entirely.
Beyond execution middleware, mods hook directly into the terminal UI layout engine. Rather than spawning external curses processes or piping raw ANSI escape sequences that risk desynchronizing the terminal buffer, mods interface with the host's terminal UI tree (such as Ink components). This enables developers to register custom slash commands backed by TypeScript execution and render interactive panes, persistent diff views, and context windows that update alongside streaming tokens. Because the UI renders inside the primary process loop, view state updates instantaneously with zero child-process thrashing.
Positioning Mods in the Agent Stack: Mods, MCP Servers, and Rules
As the agent ecosystem evolves, architectural clarity is critical. A common misconception is that in-process mods make protocols like the Model Context Protocol redundant. In practice, mods, MCP servers, and declarative rules operate at three distinct layers of the agent stack, each serving an orthogonal concern.
flowchart TB
top["Declarative Context Layer (CLAUDE.md / AGENTS.md)"]
a["Prompt Rewriting Engine"]
b["In-Process Tool Interceptors"]
c["Custom Ink TUI Panes"]
base["Out-of-Process Services (MCP Servers, Local Shell, Anthropic LLM API)"]
top --> a & b & c
a & b & c --> base
At the top sits the Declarative Context Layer (CLAUDE.md and AGENTS.md). This layer provides semantic steering: domain terminology, high-level code conventions, and project architectural patterns. It guides the model's probabilistic reasoning, but it possesses no operational authority. You cannot use a markdown file to enforce strict runtime boundaries or render custom terminal components.
At the base boundary sits the Out-of-Process Capability Layer, dominated by the Model Context Protocol alongside the host shell and external model APIs. MCP remains the ideal standard for external data integrations and language-agnostic capability distribution. If an agent needs to query a Postgres database, pull issues from Jira, or interface with a legacy enterprise service written in Go or Python, MCP handles that boundary cleanly through JSON-RPC over stdio or SSE. The out-of-process isolation protects the host agent from crashes in third-party database drivers or long-running integration tasks.
Claude Code Mods form the Orchestration, Policy, and Presentation Layer positioned between the two. Mods do not replace MCP for external capabilities; instead, they govern how the host agent ingests context, executes tools, and renders output. While an MCP server exposes what capabilities the model can call, a mod controls how the host client inspects, validates, mutates, and presents those calls. For operators implementing deterministic enterprise guardrails—similar to the architectural shifts I explored when rethinking non-deterministic routing in agent systems—in-process hooks provide the programmatic enforcement layer that static guidelines lack.
| Dimension | Claude Code Mods | Model Context Protocol (MCP) | Declarative Rules (CLAUDE.md / AGENTS.md) |
|---|---|---|---|
| Execution Boundary | In-process (Host Node.js runtime) | Out-of-process (Subprocess via stdio / HTTP / SSE) | None (Static text injected into inference context) |
| Latency Overhead | Near-zero (Native JavaScript function calls) | Transport & serialization latency (JSON-RPC over IPC) | Inference token overhead only |
| UI Customizability | Deep integration (Custom Ink/TUI panes, tabs, diff views) | None (Returns data payloads or standard markdown) | None (Text guidance only) |
| Failure Isolation | Fragile (Unhandled exceptions can crash the CLI session) | High (Server crashes do not terminate the host agent) | Total (Malformed rules cannot cause process crashes) |
| Privilege Boundary | Host ambient privileges (Full user filesystem & network access) | Restricted to exposed tool interfaces and server permissions | Strictly bounded by model interpretation |
Choosing the right primitive comes down to execution boundaries. When you need external data connectors or multi-language services, build an MCP server. When you need to communicate broad project guidelines, write an AGENTS.md. But when you need programmatic guardrails, sub-millisecond payload validation, or deep terminal customization, in-process mods are the only mechanism capable of enforcing those requirements.
The Fragility of Shared Memory: Event-Loop Starvation and Process Panics
The performance advantages of in-process execution come with a severe operational liability: the complete absence of a process fault boundary. When you execute custom TypeScript inside the host agent's Node.js runtime, your code shares the exact same thread, event loop, and memory heap as the orchestration engine and the terminal renderer. In an environment where responsiveness defines the user experience, treating the host process as an open execution playground introduces systemic stability hazards.
The most insidious of these hazards is event-loop starvation. Node.js operates on a single-threaded event loop. If an in-process mod implements a hook—such as beforePrompt or beforeToolCall—that engages in synchronous, CPU-intensive tasks, the entire host runtime grinds to a halt. In practice, I see this failure manifest when developers attempt complex code analysis directly inside lifecycle callbacks: synchronously traversing a ten-thousand-line Abstract Syntax Tree (AST), performing heavy structural code formatting, or running unoptimized regular expressions that succumb to catastrophic backtracking over large git diffs.
While that synchronous computation blocks the main thread, Node.js cannot process its microtask queue or poll for I/O events. The practical result is immediate UI degradation. Incoming token chunks arriving over Server-Sent Events from the Anthropic API back up in operating system socket buffers, and virtual DOM render updates to the terminal layout engine stall entirely. From the developer's perspective, the CLI appears to freeze mid-generation: keystrokes are swallowed, the cursor hangs, and streaming token output flatlines until the blocking task finally yields execution back to the loop.
Even more catastrophic than thread starvation is the lack of crash isolation. In an out-of-process architecture like the Model Context Protocol, an unhandled exception inside a database connector or tool server merely surfaces an error payload back to the client over JSON-RPC; the host agent survives, reports the failure, and continues its loop. In an in-process mod, however, an uncaught exception or an unhandled Promise rejection inside an early lifecycle hook—such as onSessionStart or beforePrompt—triggers a fatal runtime panic that terminates the entire Node.js process.
Because interactive CLI sessions maintain active working memory, branch navigation context, and multi-turn conversation trajectories in volatile heap space, an unhandled crash is an unrecoverable failure. The agent does not gracefully degrade; it crashes to the terminal prompt instantly, vaporizing the session history and leaving modified files or intermediate branch states in an uncommitted, indeterminate state. For production workflows, allowing unvetted third-party TypeScript to execute without defensive top-level error boundaries in the main loop turns every installed plugin into an existential risk for the entire CLI session.
Silent Mutations and Prompt Bloat: Where Chained Hooks Break Agent Reasoning
Moving middleware into the agent pipeline creates significant semantic failure modes when multiple plugins interact. In Claude Code, plugins register hooks declaratively through manifests like hooks.json. However, when multiple installed plugins register handlers for the same lifecycle event—such as beforePrompt or beforeToolCall—execution order often depends on non-deterministic factors like package registration order or filesystem traversal. Without an explicit topological dependency graph or priority declaration, chaining mutating hooks produces erratic, non-reproducible runtime behavior.
The most destructive consequence of uncoordinated hook execution is silent parameter mutation. Consider a scenario where an enterprise governance mod intercepts beforeToolCall to enforce path safety, silently rewriting relative file arguments or appending sanitization flags to a shell execution payload. From the perspective of the runtime, the mutated payload executes successfully. But from the perspective of the model's internal reasoning loop, a critical causal link has severed.
The LLM formulated its plan based on specific command parameters and anticipated an outcome tied directly to its generated payload. When the underlying execution runs with mutated arguments and returns unexpected output or fails on an altered flag, the model is presented with an error condition it never created. Because the agent cannot observe the silent rewrite performed by the middleware, its chain-of-thought reasoning breaks down. The model enters a hallucinated recovery loop, attempting to fix syntax or path errors based on its original prompt intent, repeatedly mutating commands in ways that the invisible middleware continues to alter or veto. This cycle drains the user's token allocation and frequently forces manual developer intervention to abort the session.
Equally damaging is the operational drag introduced by prompt bloat and cache invalidation inside beforePrompt. It is tempting to treat beforePrompt as a universal dumping ground for dynamic contextual awareness—injecting real-time timestamps, fluctuating git branch statuses, local resource metrics, or active task states into the prompt payload. Doing so fundamentally undermines the economics of frontier model inference.
Anthropic's prompt caching relies on deterministic, byte-for-byte prefix matching. When an in-process mod dynamically inserts changing metadata near the beginning of the prompt structure, it invalidates the prefix cache on every single conversational turn. Instead of reading hundred-thousand-token repository maps and system instructions from the cache at a fraction of the cost, the host is forced to submit a cold prompt on every interaction. The operational penalty is immediate: token costs balloon by orders of magnitude, and time-to-first-token latency degrades from hundreds of milliseconds to multiple seconds. What was intended as a lightweight context enhancement becomes an expensive architectural tax that degrades agent reasoning speed and inflates production budgets.
Unsandboxed Execution: The Threat Model of In-Process Plugins
When evaluating in-process plugins from an operational security perspective, the trade-off becomes stark: what gives mods their sub-millisecond responsiveness also makes them an uncontained liability on developer workstations. In an architecture governed by the Model Context Protocol, integrations run as isolated child processes or remote services communicating over standardized JSON-RPC envelopes. If an MCP server misbehaves or attempts an unauthorized action, the client runtime can intercept the message over the transport layer, enforce capability boundaries, or terminate the offending process without compromising host integrity.
Claude Code Mods operate under an entirely different execution model. When you run /plugin install, the runtime fetches and evaluates compiled TypeScript directly inside the primary Node.js process. There is no hypervisor, no V8 isolate boundary, and no operating system container restricting what that code can touch. The mod immediately inherits the ambient execution privileges of the developer who launched the terminal session.
This lack of isolation creates a severe privilege escalation vector that bypasses Claude Code's interactive permission gates. The permission prompts developers are accustomed to—approving a generated bash command or confirming a file overwrite—exist purely to govern the LLM's tool calls. They do not monitor or restrict the host Node.js runtime itself. An in-process mod does not need to ask the model to run a bash tool to inspect the local system; it can import node:fs and read ~/.ssh/id_ed25519, inspect ~/.aws/credentials, or parse uncommitted .env files in memory during the onSessionStart hook.
Worse, network access from within a mod is completely invisible to the agent's permission pipeline. A mod can execute standard Node fetch calls or establish outbound TCP sockets to exfiltrate parsed credentials, local diffs, or prompt context to a remote endpoint without ever surfacing an approval dialog in the terminal UI. Because execution happens in-process, network traffic originates directly from the trusted host binary.
The supply chain exposure makes this threat model critical for engineering organizations. Packaging mods inside plugins managed via package registries inherits the full systemic risk of the npm ecosystem: typosquatting, account takeovers, and transitive dependency poisoning. If an installed plugin pulls an updated sub-dependency containing malicious post-install hooks or obfuscated payload dispatchers, that code executes directly on the engineer's workstation with full access to internal source repositories and cloud keys.
For enterprise environments, the operational rule must be unequivocal: treat mods not as lightweight UI themes or configuration tweaks, but as unsigned binary executables running with root-equivalent host context. Unvetted third-party plugins have no place in production workflows without private registry mirrors, mandatory static analysis of transitive dependencies, and rigorous source code auditing.
Applied Engineering Patterns: Three High-Leverage Mods to Build
Understanding the operational boundaries of in-process execution allows us to design high-leverage mods that deliver deterministic control while avoiding event-loop blocking and cache degradation. Rather than treating mods as open-ended script runners, the most effective implementations focus on targeted middleware interventions that resolve daily friction in engineering workflows.
A primary pattern I recommend deploying is a Deterministic Secret-Scrubbing and Shell Command Guard Mod. Instead of hoping an instruction in CLAUDE.md prevents the model from inspecting sensitive files or running destructive shell operations, this mod acts as an in-process gatekeeper bound to the beforeToolCall hook. When the model emits a payload targeting the bash tool or file system write utilities, the mod parses the command against an in-memory abstract syntax tree and evaluates it against deterministic regex baselines, searching for high-risk flags, credential exposure, or forbidden directory traversals before host execution can occur.
The critical implementation detail here is avoiding silent parameter mutation. If your guard hook secretly rewrites file paths or appends safety flags to a bash command, the model's internal reasoning loop disconnects from reality; when an altered command fails, the agent attempts to recover based on code it never actually ran. Instead, the hook should reject violating commands explicitly, returning clear, descriptive error strings into the tool result channel. This gives the agent immediate causal awareness of the policy violation, allowing it to adapt its plan without manual developer interruption.
A second high-value architectural pattern is a Cache-Preserving Dynamic Context and Test Diagnostics Injector. Engineering workflows frequently require injecting real-time state—such as failing test outputs, local compiler diagnostics, or git branch metadata—into the agent's context. However, naively injecting dynamic text into the top of the prompt inside beforePrompt invalidates Anthropic's prefix cache on every conversational turn, triggering massive token cost spikes and latency degradation.
To solve this, the diagnostics mod hooks into both afterToolCall and beforePrompt, maintaining an in-memory ring buffer of recent test runs and linter failures. When updating the prompt payload, the mod leaves the system prompt prefix completely untouched to preserve the byte-for-byte cache alignment. Instead, it appends the diagnostic ring buffer strictly to the tail of the payload within the latest conversational turn. The agent receives fresh, deterministic test feedback without destroying the economics and speed of cached model inference.
Finally, teams can significantly streamline local review workflows by building an Interactive Terminal Code Review and Diff Approval Pane. Default terminal agent diffs tend to dump raw inline ANSI text across the screen, forcing developers to scroll back through cluttered terminal history to evaluate file mutations. By leveraging the runtime's UI render hooks alongside beforeToolCall events on file editing tools, this mod intercepts pending writes and mounts an interactive, keyboard-navigable split-pane directly within the terminal layout engine.
The mod halts execution while presenting a clean side-by-side diff view, allowing engineers to review, accept, or reject specific line ranges before modifications commit to disk. When building this UI layer, the essential operational discipline is isolating render cycles from the primary event loop. Handlers must listen carefully to terminal resize events (SIGWINCH) and avoid synchronous blocking during virtual DOM layout reflows; failing to handle curses screen buffer bounds gracefully will corrupt the terminal stream, swallowing user input and freezing the interactive session entirely.
Resources
Updated 2026-09-30 by Mehran Mozaffari.
Related posts
28 October 2026
The Codebase Is an Environment, Not a Context Window
24 October 2026
Recurrent State Kernels Move the Local Inference Bottleneck from Memory to Dispatch
4 October 2026
Turning Production Coding Agents into RL Environments: The Mechanics, Failure Modes, and Economics of Capture Proxies
15 September 2026
From Static Mesh to Walking Character: A Technical Operator's Manual for the 3D Vibe Coding Pipeline
9 September 2026
How I'd Build a Real-Time Conversational Avatar: GPT-Live, LiveAvatar, and the Tool-Call Overlay Problem
9 September 2026
GPT-Live-1 + LiveAvatar: What It Actually Takes to Ship a Real-Time Avatar Language Tutor
