I already know how to write settings-based PreToolUse/PostToolUse hooks — is it worth specifically learning Mods?
Not necessarily. The two solve problems at different layers: if your need is "check before or after a specific tool call, block or log certain behavior," the settings-based hook system is already entirely sufficient, with a simpler architecture and no need to write JavaScript/TypeScript.
The point where learning Mods actually pays off is when you find your need is something settings-based hooks architecturally can't touch at all — like continuously displaying a live status on screen (a real-time spend bar like burn-meter), or needing a slash command that fires instantly without waiting for a full conversation turn. These needs fall under interface rendering and in-flight event rewriting — settings-based hooks run outside the process and architecturally can't see any of that; only Mods, running inside the Claude Code process itself, can reach it.
How do I actually choose between observe, rewrite, and answer in practice?
The choice comes down to whether you want the event to keep propagating, and whether you need to change its content. If you just want to "know" an event happened (logging when a tool call occurred, say), use observe — call next, pass it through unchanged, no effect on downstream behavior, lowest risk.
If you want to change the event's content but still let it continue through the normal flow (automatically appending extra context before a prompt is submitted, for instance), use rewrite — call next with a modified copy. If you want to intercept directly and stop the event from propagating further (detecting a dangerous operation and returning a rejection result immediately), that's when you use answer — don't call next, halting the chain right there. Risk and scope of impact increase in that order, so it's worth starting your first Mod with observe to confirm your event-subscription logic works, before moving on to rewrite and answer.
The documentation says a hook that throws "fails quietly" — what risk does that actually create in practice?
It means your Mod could have been broken for a while with no obvious warning on screen at all — because a failure only leaves a dim, easy-to-miss line in the transcript, with no error popup or interruption to whatever you're doing. If your Mod handles something security- or monitoring-related (detecting a dangerous command, say), this "silent failure" behavior is especially risky, because you might assume the protection is still active when it's actually stopped responding entirely.
The practical response is to treat testing as mandatory, not optional: every time you modify a Mod's hook logic, deliberately trigger the scenario that's supposed to fire that hook, and personally confirm it responds correctly — rather than assuming everything's fine just because no error message appeared.
My Mod's on-screen state disappears every time it reloads — what did I get wrong?
The most likely cause is exactly the pitfall the documentation flags: storing values that need to persist in the module's own variable rather than the session's state store. Claude Code supports hot reload, letting you apply Mod code edits without restarting the whole session — but hot reload resets the module's own variables. If your state lives in a plain JavaScript variable, that data vanishes the moment a reload happens.
The fix is to switch to the state storage mechanism the engine provides, reading and writing data that needs to persist (burn-meter's cumulative spend, code-pet's current status) through that mechanism instead of declaring it as a plain top-level module variable. That way, even repeated code edits and hot reloads won't reset what's shown on screen.
Claude Code v2.1.287 (released October 1, 2026) introduced a new layer of extensibility called Mods. The official framing — "plugins may now modify deeper behavior" — sounds like a routine upgrade to the plugin system, but pulling it apart shows Mods operate at a fundamentally different layer than regular plugins, or even the existing settings-based hook system.
Per a technical breakdown, a Mod is essentially an ordinary Claude Code plugin with one extra file — the hooks module — which exports a function called register. A complete Mod consists of three core files: a plugin manifest, a hooks.json pointer to the hooks module, and the module itself. Notably, Mods require no build step at all — Claude Code loads .js and .ts files directly.
The key difference is where it runs. The existing settings-based hook system (PreToolUse, PostToolUse, and the rest, configured in settings.json) runs shell commands, HTTP requests, or prompts outside the Claude Code process itself, responding to lifecycle events. Mods instead run directly inside the Claude Code process, with direct access to prompt content, tool calls, and even interface rendering — territory the settings-based hook system architecturally cannot reach at all.
A Mod subscribes to named events through handlers following a fixed three-argument pattern: the mods API, the event itself, and a next function. Handlers chain together and can do one of three things: observe (call next, passing the event through unchanged), rewrite (call next with a modified copy, affecting downstream behavior), or answer (return a result directly without calling next, halting the chain right there). The system currently covers seven event categories, including tool.call, prompt.submit, turn.step, and ui.render.
The official documentation is direct about the capability boundary: "only a mod can draw a pane, add a slash command that runs at once without a Claude turn, or rewrite an event in flight" — all three of these are things external extensions simply cannot do, by architecture.
The first is code-pet: a pixel-style creature displayed next to the model picker, hooked into tool calls and test results — it turns "sick" after failed tests and "panics" on destructive commands. This visual indicator lives in the status line, requiring the Mod to track session-level state rather than a plain module variable.
The second is inbox-alerts: it polls Gmail, Slack, and Calendar through claude.ai connectors and surfaces new items as toast notifications and a persistent pane — toasts are temporary, while the pane stays put, letting you see incoming work without leaving the terminal.
The third is burn-meter: a dedicated pane showing a growing fire bar for session spend, plan-limit bars with reset times, and the cost translated into burger and cheeseburger counts. This pane renders continuously, requiring the Mod to read state from the engine rather than storing it locally.
The first pitfall is about error handling: a hook that throws an exception "fails quietly," leaving only a dim, easy-to-miss line in the transcript — no obvious error message pops up. That makes testing especially important when writing a Mod, since failures won't proactively announce themselves.
The second pitfall is about how state gets stored: a common mistake is storing values that need to persist in a module variable rather than the session's state store — because a hot reload resets the module's own variables. A continuously-rendering pane like burn-meter has to use the engine's state storage mechanism, or the on-screen information vanishes the moment code gets edited and reloaded.
If your current need is just checking before or after a tool call, or blocking a dangerous command, the settings-based hook system (PreToolUse/PostToolUse) is already entirely sufficient — there's no need to jump to writing a Mod. But if what you want is something at the visual layer — a pane that continuously shows status, a slash command that fires without waiting for a full conversation turn, or an effect that requires rewriting an event's content in real time — those needs can only be met at the Mods layer of extensibility, which is exactly why the documentation gives it a separate name from the regular plugin system.