AIDive

I Built An Undocumented Claude Code Mod. A Guard Fails Open

By AIDive · Published

Coding agentsAI security

Twelve days from 'if you want this' to Tetris

Claude Code Mods are TypeScript modules that run inside the Claude Code engine and hook its events as functions. Boris Cherny, who leads Claude Code at Anthropic, announced them with five words: "Claude mods are landing now." The post collected 2,400 likes, and someone had already built Tetris inside the terminal. Tetris, Doom, and a pet that grows while Claude runs your tests, all rendered above the prompt, at zero tokens.

The announcement links no docs page. It links a GitHub issue opened 12 days earlier by an Anthropic engineer, with one condition written into it. Alice Poteat wrote that the response from the community would likely dictate whether the feature ships or not. 177 comments later, people had hammered the flagged binary, measured its timeouts, and built games on it.

Measure Value
Likes on the announcement 2,400
Comments on the GitHub issue 177
Typed surface in this week's build 10,700 lines
Events on nouns (issue-thread build) 84 on 19
Mods on GitHub inside 48 hours 31

The docs page at the time of writing returns a 404. This article covers the four ways to extend Claude Code, one useful Mod built and broken on purpose, and what is still unstable before you ship one.

A Mod is a function in the middle

The source tree defines it in one sentence: a Mod is a Claude Code plugin whose behaviour lives in a hooks module. One register entry hooks the engine's events as functions. On disk, that is a plugin folder, a hooks manifest naming exactly one module, and the module itself.

Every hook is a function of three things: $, the door every side effect goes through; the event; and the continuation, the rest of the chain beneath you. Hooks nest like middleware. The one registered first owns the event, and nothing further down the chain can inhibit it. That order is configured, not installed. In Alice Poteat's words, order is set by configuration, not by when you installed them.

There is no ambient access. Everything a Mod does goes through $, so an admin can audit, allow, deny or log any event. One commenter summarised it: what a plugin did is exactly the calls it made. A single hook on * sees every event, so an audit log is one function. A Mod can draw, because the interface is React. It runs in process on Bun, at 50 microseconds at the 99th percentile. One environment variable turns it on. What actually lives on $ is still being designed with early-access partners.

Four ways to extend Claude Code, and which one for what

A shell hook is a script the engine calls at a fixed moment. It gets JSON on standard input and answers with an exit code. The answer has a ceiling: 8,000 characters of context back, and 2,000 for some hooks. In Pratham's words, on Windows they often break in weird ways.

A plugin is a box. One manifest bundles skills, agents, hooks, and MCP servers. It is installed from a marketplace or loaded straight from a folder on disk, and one command validates the box before anything runs.

A skill is prose the model reads when it needs it. The body loads only when a matching prompt arrives. It remains the cheapest way to change behaviour.

A Mod is the same box with one more file: a hooks manifest naming a module, and that module is TypeScript, typed, in process, on every engine event. That is the whole difference.

Mechanism What it is Runs Ceiling
Shell hook Script called at a fixed moment Subprocess, exit code 8,000 chars back (2,000 for some hooks)
Plugin Bundle of skills, agents, hooks, MCP servers Installed or loaded from disk Validated before it runs
Skill Prose loaded on a matching prompt In the model's context Cheapest change
Mod Plugin plus a TypeScript hooks module In process, on every event Typed, early access

The types come from one slash command, which writes the full listing of what $ offers straight into your project. The old shell hooks are wrapped, not retired. On an early build, Spencer Morley watched the wrapper fail to load and stay declared. The rule of thumb: to change what Claude knows, write a skill; to run a script at a moment, a shell hook; to ship a bundle, a plugin; to sit inside the engine, a Mod. The shell hook still works everywhere the flag is off.

Anthropic's own three, read from source

Three Mods ship inside the binary and their source is on GitHub: a security default, a diff pane, and telemetry.

The security default sits outermost. On a machine with managed settings, or in a Team or Enterprise organisation, nothing a person installs gets above it. It hooks 12 events, and every hook makes one of three moves: continue past the user tier, refuse a caller from the user tier by name, or pass. It fails closed, and the doc comment says so in two words. That is the part the thread cared about: an admin removes an affordance from $, and nothing registered below can invoke it. In one commenter's words, that is categorically different from asking a plugin not to do something.

Diff is a pane beside the transcript, showing the session's uncommitted changes file by file, refreshed as Claude edits. It is registered on session start and spans 27 helper files, not a toy.

Telemetry adds a noun to $ from inside engine creation. It awaits what is beneath and returns that plus itself. It runs in internal builds only.

The README says to run one from source and test one from source. The help on today's build lists validate, eval, and details. There is no test in the list, even though the test help still answers. The built-in tier also refuses your copy: ship a plugin with one of those names, and the binary loads its own.

Forty-two lines: a Mod that hides secrets from the model

The slash command that writes the types produces, on this build, 11,700 lines: 84 events across 23 nouns. The tool call hook answers with a result or a deny, never the text. That part is set by core.

The Mod is three files: the plugin manifest, the hooks manifest with its one line, and the module. The module is 42 lines. It awaits what is beneath, scrubs the result, and hands it back. Four patterns cover two vendor key shapes, a GitHub token, and anything assigned to a variable called key, secret or token.

Validate reads the source before it runs. It names the event the module hooks and the one thing it calls on $. The only warning is a missing author. Loaded from disk with the flag on, the test file holds two keys, both fake by construction, and the model reads "redacted". In its own words, the values came back as redacted, so it cannot see what is in them.

Measure Value
Module length 42 lines
Hop latency, worker included 28 ms
Tokens added to the session 0

The engine logs the hop as resolved by a hooks module. The inventory shows no skills, no agents, nothing always on. One limit remains: hidden from the model is not hidden from the screen. The transcript still shows what the tool printed. That is a different Mod, as Max at Ship Notes noted in one line.

Break it: a slow guard is a bypassed guard

Same Mod, one added line: a 15-second sleep before the call to what is beneath. Ten seconds later, the engine gives up on it, reports that the hook exceeded its budget and was skipped, and what is below it runs in its place. The command runs anyway and prints "hi".

Replace the sleep with a throw: 574 milliseconds, same verdict, skipped, and the command runs.

Case Time Verdict
Healthy hook 28 ms Resolved
Throw 574 ms Skipped, command ran
Hang over 10 s Skipped, command ran

Both failures end in the same word: skipped. The thread had measured the asymmetry a week earlier. A capability missing at load fails closed. A hook past its budget fails open: loud but bypassed, in Spencer Morley's words. A block with no reason just sends the model to another tool. Pratham watched it pick a different tool and write the file anyway.

The answer on the table is a catch. Alice Poteat proposes a catch on the hook's return, which runs if you take too long or throw. A rewritten write still needs a note to the model: it sees what it asked to write, for caching reasons, so you append a context line. The budget is also the isolation. The hooks worker runs apart, and when it crashes, the engine respawns it and turns hooks off for the session. The fix is not a longer budget but a declared catch.

Forty-eight hours of Mods, and what one sentence buys

Two days in: Tetris above the prompt and seven other games, playable while Claude works, at zero tokens. Doom, the 1993 original, runs in its own process; the Mod reaches it over local HTTP and redraws 10 times a second. There is a breathing pacer in the spinner, and a story model with 260,000 parameters running inside the hooks worker, with zero API tokens.

The registry scanned 31 Mods with validate. 14 can run host processes. 13 see every tool call.

Demo 8 of the community thread promised that one sentence writes a plugin that hides secrets before the model reads them. We asked for one.

Measure Value
Time 4 min
Turns 34
Cost $1.23
Lines (plus a test file) 190, against our 42
Tests 4 pass in a third of a second

It labels what it hides by kind, validates clean, and needs no API key and no model call. One reply put it this way: allowlists are the product, Tetris is the demo.

Verdict: build one now, ship one later

The registry's own numbers are the warning. 14 of 31 Mods can run host processes, and the only vetting is a static footprint. The eval help says so itself: a passing suite is not a security vetting.

Signal Value
Events on the 8th 20
Events on the 15th 84
CLI versions in a fortnight 14
Hacker News points 2
Replies to the compatibility question 0

The 10-second budget lives in the runtime, not in the types, as Marat noted. There is no changelog line, no docs page, no launch post. A rival says Anthropic copied it: Ahmad Awais points at Command Code's mods, and he writes their example mod, so weigh that. The "early access, APIs may change" post drew 357 likes.

Build one now if you already write hooks and want audit, redaction or a pane. Wait if you are shipping to a team before the contract is written. The same openness is why the budget that fails open, the MCP call type mismatch and the wrapper failure were found by users in days.

Sources

Frequently asked questions

What is a Claude Code Mod?
A Mod is a Claude Code plugin whose behaviour lives in a TypeScript hooks module. The module registers functions on the engine's events and runs in process on Bun, with every side effect going through the `$` object.
What is the difference between Claude Code hooks, plugins, skills and Mods?
A shell hook is a script called at a fixed moment that answers with an exit code. A plugin is a bundle of skills, agents, hooks and MCP servers. A skill is prose the model loads on a matching prompt. A Mod is a plugin with one extra file, a typed TypeScript module that hooks every engine event in process.
Can a Claude Code Mod hide secrets from the model?
Yes. A 42-line Mod that awaits the tool result, scrubs key, secret and token patterns and returns the redacted text adds 28 ms per hop and zero tokens. It does not hide the values from the transcript on screen, which needs a different Mod.
Why does a slow Claude Code Mod fail open?
The hooks worker gives each hook a 10-second budget. A hook that exceeds it, or throws, is marked skipped and the chain beneath runs in its place, so the guarded command executes anyway. Anthropic's proposed fix is a catch declared on the hook's return.
Are Claude Code Mods safe to install?
Not yet by default. Of the 31 Mods indexed in the first 48 hours, 14 can run host processes and 13 see every tool call, and the only vetting is a static footprint. Managed settings put Anthropic's security default above anything a user installs.
Should I build a Claude Code Mod now?
Build one now if you already write hooks and want an audit log, redaction or a diff pane. Wait before shipping one to a team: the feature is early access, the API changed from 20 to 84 events in a week and there is no docs page or changelog line yet.

Related videos