Between August and October 2026, Chien Vu Minh opened 60 Claude Code sessions. In 20 of them, he manually typed the same reminder: don’t print the API key. Across 66 sessions, he watched Claude edit plan files 408 times and open a file to check progress almost as often. In two sessions, Claude marked 13 tasks complete. Twelve of those had no passing test after the latest code changes.
His fix was to stop relying on chat reminders and start using mods: small TypeScript functions that run inside Claude Code’s own process. On 1 October 2026, Anthropic shipped mods in Claude Code 2.1.287. This is what he built and how each one works.
What are Claude Code mods, and why do they matter?
Before mods, Claude Code automation lived outside the model. MCP servers, settings hooks, custom subagents, and skills all ran as separate processes. Settings hooks cost 55–99 milliseconds per check on Chien’s Ryzen 9 5900X laptop (Windows 11 Home) because each one launched a fresh process.
A mod runs inside Claude Code’s process. It attaches to events like tool.call, prompt.submit, and ui.render, intercepts them, and responds without spawning anything. The same check that cost 55–99ms as a settings hook costs 0.27ms as a mod hook in the test harness. In a live session, the median across five calls was 0.6ms, including communication overhead. That’s roughly 100 times faster than the cheapest fresh-process approach.
Anthropic engineer Alice Poteat, who proposed the system on 3 September 2026, described the difference this way in her architecture document: a shell hook depends on its environment, while a function hook is a contract with the engine. The idea shipped as Claude Mods six days after the proposal and landed in Claude Code on 1 October.

️ How to build your first mod in 6 steps
You need Claude Code 2.1.287 or later. The easiest path is to ask Claude directly: Make a mod that blocks cat .env. Claude uses its built-in plugin-authoring skill to create it. Approve hot reloading when prompted. To keep the mod after the session, copy its folder from ~/.claude/dev-mods/ to your own directory.
For the manual path, a mod needs three files:
env-guard/
├── .claude-plugin/plugin.json
└── hooks/
├── hooks.json
└── register.js- Create the folders:
mkdir -p env-guard/.claude-plugin env-guard/hooks - Add the plugin manifest at
env-guard/.claude-plugin/plugin.json:{ "name": "env-guard", "version": "0.1.0", "description": "Refuses commands that print the .env file", "author": { "name": "Your Name" } } - Register the mod at
env-guard/hooks/hooks.json:{ "description": "The env-guard hooks module", "modules": ["./register.js"] } - Write the hook at
env-guard/hooks/register.js:
Theconst READS_ENV = /bcats+.envb/ export function register(on) { on('tool.call', { tool: 'Bash' }, async ($, e, next) => { if (READS_ENV.test(e.command)) { return { deny: 'Do not print .env. Use source .env and $NAME instead.' } } return next(e) }).catch(async ($, e, next) => { return { deny: 'env-guard failed, so this command was not run: ' + next.error.kind } }) }.catchblock makes this guard fail closed. Without it, a hook crash lets the action through. - Validate it:
claude plugin validate ./env-guard
Look fortool.callwith the Bash filter in the output. - Test it:
claude --plugin-dir ./env-guard
Ask Claude to show you what’s in your.envfile. If it triescat .env, the mod blocks it.
Mod 1: secret-guard
secret-guard loads .env values into memory at session start, searching from the working folder up to the drive root. It adds a standing note to prompt.context that names the protected keys, explains the ‹hidden: NAME› markers, and tells Claude to use $NAME in commands rather than print values.
Commands like cat .env or a bare env are blocked outright. Other commands run, but secret-guard intercepts their output before Claude sees it and replaces recognized secrets with ‹hidden: NAME›. For example, after grep -r KEY ., Claude sees .env:DEMO_API_KEY=‹hidden: DEMO_API_KEY›.
Chien tested two modes against 369 real calls from his 60 sessions (none of which exposed a key) and 12 crafted leaks using fake keys:
| Mode | Harmless calls refused (of 369) | Leaks stopped (of 12) |
|---|---|---|
| Command mode | 157 | 7 |
| Value mode (default) | 2 | 10 |
Command mode blocked 157 harmless calls and missed 5 leaks, including a recursive grep and four commands that loaded .env before printing a value. Value mode blocked 2 calls (both attempts to write a hidden marker into a file) and stopped 10 leaks. It missed a base64 copy and an isolated 8-character prefix that didn’t look like a secret.
This is not a security boundary. For files Claude must never read, use permission deny rules, sandboxing, file permissions, or a secrets manager. The benefit here is an automatic check instead of a manual reminder repeated in 20 of 60 sessions.
secret-guard was tested with 58 tests on Claude Code 2.1.296 and passes all of them.
⚠️ Mod 2: done-gate
done-gate watches code file edits, shell commands, and task-list updates through tool.call. If Claude marks a task complete without a passing test run after the latest changes, it appends a warning to the tool result and screen:
done-gate: “Add double()” was marked done, but 1 code file was changed and no test has run in this session: calc.py. Before you report this task as finished, run the tests …
Unlike secret-guard, done-gate never blocks anything. It adds a note and lets Claude decide. Chien chose this design deliberately: the 12 unmarked tasks in his sessions were experiment scripts without a test suite, so a missing test wasn’t necessarily a problem. A warning is the right response when the rule doesn’t apply everywhere.
Replaying those sessions showed done-gate would have warned on 12 of the 13 completions. An early version mistakenly counted echo pytest as a test run, which was caught during review. The current version passes 23 tests on Claude Code 2.1.296. If done-gate crashes, its .catch lets the original tool result through unchanged.
Mod 3: plan-meter
plan-meter draws above the prompt and in a side pane. The status line looks like: plan ▸ Demo plan · phases 0/2 · steps 1/4 (25%) · now: Build. It watches tool.call for plan file edits, reads the updated file, and stores progress in session state. Claude Code redraws the display automatically.
Across 66 sessions, Claude edited plan files 408 times and read them 119 times. plan-meter tested against 21 plan files: it found step counts in 10 and status lines in another 10. It passes 28 tests on Claude Code 2.1.296, including one where checking off a step updated the display from steps 1/4 to steps 2/4. In non-interactive mode (claude -p), it returns a single text line instead of drawing.
Rendering happens outside the model, so it uses no tokens.
Mod 4: context-meter
context-meter shows context window usage after each turn, a prompt-cache countdown, and a Compact button. It pulls from $.session.usage, the same data shown by /context.
The prompt cache lasts one hour on a subscription or five minutes with an API key. Since the mod can’t detect which applies, it shows its assumption: cache ▸ 40:00 left (1h TTL, assumed: subscription).
The Compact button is the only feature across all four mods that calls the model. It asks Claude to summarize the conversation to free up context. In one session, compaction reduced usage from 211k to 132k tokens. In a live test, the display dropped from 178k to 98k tokens immediately. Version 0.1.5 fixes a bug where an earlier version checked usage too soon and missed the change. The mod passes 26 tests on Claude Code 2.1.296.

Security note: mods are not sandboxed
Mods don’t access files or networks directly. They use declared $ calls like $.fs.read or $.process.run. Because calls are declared in code, claude plugin validate can list them before the mod runs. secret-guard declares ten calls, including $.fs.list and $.fs.read to inspect env files, and makes no network calls.
But mods run with your permissions. File operations don’t trigger tool permission prompts. Chien confirmed this when a test mod wrote to his home folder without asking. Claude Code’s bash sandbox doesn’t cover programs started by mods. Check the calls: list with claude plugin validate before installing any mod from an external source. Anthropic can also disable installed mods remotely.
How to install the kit
All four mods are published in the open source claude-code-mods-kit on GitHub under an MIT license. The kit also includes four additional mods built from ideas in a third-party YouTube walkthrough.
To try a mod for one session without installing:
claude --plugin-dir claude-code-mods-kit/plugins/plan-meterFor a permanent installation:
claude plugin marketplace add vumichien/claude-code-mods-kit
claude plugin install secret-guard@chien-mods
claude plugin install done-gate@chien-mods
claude plugin install plan-meter@chien-mods
claude plugin install context-meter@chien-modsThen type /secret-guard on, /plan-meter on, or whichever mods you need. Each supports /<mod> on, /<mod> off, and /<mod> status. When disabled, a mod passes every event through unchanged with no model calls, file writes, or messages to Claude. The setting persists across sessions and projects.
Pro Tip: find your own mod ideas
Claude Code stores session transcripts locally. Review a few and look for instructions you keep repeating. Chien found his patterns by scanning 60 sessions: 20 manual key reminders, 408 plan file edits he had to track manually, and 12 task completions without a test. Those repetitions pointed directly to the four mods above.
If you’d rather skip the review, developer Ben Newton had Claude read 30 of his own sessions and suggest ten mods, then build five of them. His post includes the prompt he used.
Not every repeated instruction needs a mod. Things like “write tests first” or “keep answers simple” belong in CLAUDE.md. Use a mod when something needs to run at every step, change what Claude sees, or display information as it works. For security rules, use permission deny rules, sandboxing, file permissions, or a secrets manager. A mod hook is not a security boundary.



