# Claude Code Mods: Five Tips From the Mods People Built

> Claude Code 2.1.287 (October 1, 2026) shipped Claude Mods: plugins whose hooks are JavaScript or TypeScript functions that run inside Claude Code and can draw panes and a band above the prompt, hold or rewrite tool calls, and add commands. Mods are on by default, and the environment flag from the September early access is now ignored. The five tips: let Claude write your first mod, hold risky Bash calls with $.ui.ask, use the band above the prompt, run claude plugin validate before installing anything, and learn from the four built-in mods Anthropic publishes.

URL: https://blog.bokvi.com/blog/claude-code-mods-tips/
Published: 2026-10-02
Tags: ai-coding-tools, claude-code, anthropic, dev-workflow

**Claude Code 2.1.287, released on October 1, lets a plugin carry a mod: JavaScript or TypeScript functions that run inside Claude Code and can redraw its interface, hold a tool call, or answer an event in Claude Code's place. "[You can now mod Claude Code](https://x.com/ClaudeDevs/status/2105721434807083061)", as the official ClaudeDevs account put it, and the source of four of Anthropic's own mods is public in the [`mods` folder](https://github.com/anthropics/claude-code/tree/main/mods) of the Claude Code repository.**

I read that folder, the [official docs](https://code.claude.com/docs/en/plugins/mods), the month-long [design thread](https://github.com/anthropics/claude-code/issues/91870) and the mods people shipped during early access, then wrote and ran a couple of my own. Below are the five things I would tell someone writing or installing their first mod.

![The Claude Code prompt box with a band above it that reads Clear, 10% of context, 19.2k of 200k tokens, a small sparkline of the last turns and plus 19.2k last turn](https://blog.bokvi.com/_astro/token-weather-band.DLLx-qqQ_gOGjM.webp)

*Token Weather, one of the demo mods in the launch video: a band above the prompt that reports how full the context window is, so you never have to type /context. Frame from ClaudeDevs's Claude Mods launch video.*

## A mod is a plugin with one extra file

A mod is an ordinary [plugin](https://code.claude.com/docs/en/plugins/overview) whose `hooks/hooks.json` names a module. Claude Code calls that module's `register(on)`, and every `on('event', fn)` adds a function to a middleware chain, Express-style. Each function gets the mods API as `$`, the event as `e`, and `next`. Return `next(e)` to let the event through or `next({ ...e, ... })` to change it. Return your own result instead and you have answered it: Claude Code's own behaviour never runs.

The feature moved fast. Anthropic's Alice T'Poteat [proposed "function hooks" on September 3](https://github.com/anthropics/claude-code/issues/91870), the product got the name Claude Mods on September 9, and Anthropic's Boris Cherny [announced on X](https://x.com/bcherny/status/2099551291601248485) that they were "landing now" on September 14. Early access still needed an environment flag. Version 2.1.287 [made them a release feature](https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md). That history matters for one practical reason: almost every community README and article I read, from [Wavect](https://wavect.io/blog/claude-mods-function-hooks/) to [Neurycode](https://neurycode.com/blog/claude-code-mods), still tells you to set `CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1`. Since 2.1.287 that variable [is ignored](https://code.claude.com/docs/en/plugins/mods#turn-mods-on-or-off), mods are on by default, and setting it to `0` will not turn them off. Use `/plugin`, `--safe-mode` or `disableAllHooks` for that.

## 1. Let Claude write your first mod

You don't need to learn the API before you have a mod running. Claude Code ships a built-in `plugin-authoring` skill that knows which events and methods your version has, and Claude loads it when you ask for a mod in plain words. The [create guide](https://code.claude.com/docs/en/plugins/mods/create#ask-claude-for-a-mod) uses `make a mod that shows the current git branch above the prompt` as its example. Claude writes the files into `~/.claude/dev-mods/<session-id>/<name>/`, Claude Code asks once whether to enable hot reloading for the session, and from then on the mod reloads at the end of every turn that changes it. Anthropic's own demo of this, "One sentence. One plugin.", is in the [original proposal](https://github.com/anthropics/claude-code/issues/91870): one short ask, and Claude writes, validates and loads a plugin that replaces secrets in tool output before the model reads them.

The prompt matters more than you'd think. Vox [posted a template on X](https://x.com/Voxyz_ai/status/2099564071972450641) that I'd copy almost word for word: describe the behaviour you want, then ask Claude to check whether your version supports it, "first look for an existing Mod or setting that can do it", propose the smallest implementation and a way to test it, and wait for your confirmation before installing anything. That last sentence keeps a mod from growing into something you didn't ask for. If you'd rather have a dedicated skill, Dan McAteer published [`/claude-mod-builder`](https://github.com/DannyMac180/skills).

Two things catch people out. A mod Claude writes loads only in the session that made it, and Claude Code deletes that session's folder after `cleanupPeriodDays`, so copy anything you like to a folder of your own and run it with `claude --plugin-dir ~/mods/git-branch`. Then keep developing against that folder, not an installed copy, which Claude Code caches by version. Reloads are also less uniform than they look: one early tester [found](https://github.com/anthropics/claude-code/issues/91870#issuecomment-5670161416) that `claude -p` always loads fresh code while an interactive session kept running the old module, so a headless check passed against a fix the session never loaded. If a change doesn't seem to take, look for the reload line in the transcript before you debug.

## 2. Hold the risky tool call and ask

A settings hook can block a tool call or hand it to the permission prompt. A mod can hold it and ask a question of its own. A `tool.call` hook runs before the permission check, and it can `await` a question to you before it decides whether to call `next`. This is the mod I wrote, adapted from the [events guide](https://code.claude.com/docs/en/plugins/mods/events#hold-a-tool-call-until-the-user-decides):

```js
// hooks/register.js
const RISKY = /\brm\s+-rf?\b|\bgit\s+reset\s+--hard\b|\bgit\s+push\b.*--force/

export function register(on) {
  on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
    if (!RISKY.test(e.command)) return next(e)
    let answer = 'Refuse'
    try {
      // The tool call waits here, and the wait doesn't count against the hook's time limit
      answer = await $.ui.ask('Run this command? ' + e.command, ['Run it', 'Refuse'])
    } catch {
      // Dismissed, or a claude -p run with nobody to ask
    }
    if (answer !== 'Run it') {
      return { deny: 'The user declined this command. Ask before trying another way.' }
    }
    return next(e)
  }).catch(async ($, e, next) => {
    // The guard threw or timed out: refuse rather than let the command through
    return { deny: 'The command guard failed (' + next.error.kind + '), so the command did not run.' }
  })
}
```

![A Claude Code session where Claude is about to run rm -rf build, and a dialog labelled Plugin asks Run this command? rm -rf build with the options Run it, Refuse, Type something and Chat about this](https://blog.bokvi.com/_astro/bash-guard-ask.BZbwMV-e_Z1v49Wi.webp)

*The guard holding `rm -rf build` in a real session. The question appears in the same dialog Claude uses when it asks you something.*

The `.catch` is the part to copy even if you change everything else. A hook that throws or runs past its [10-second limit](https://code.claude.com/docs/en/plugins/mods/events#handle-a-hook-that-fails) is skipped, and the next handler runs in its place, so a broken guard fails open and the command runs. The `.catch` handler gets one second to answer instead, and here it refuses.

The other lesson came from what happened after I picked Refuse. The deny text is all Claude learns: as T'Poteat [put it in the thread](https://github.com/anthropics/claude-code/issues/91870#issuecomment-5546290346), "to make the tool not get called, don't call `next(e)`", and whatever you return is what the model is told happened. Claude, on Haiku 4.5, told me "the permission system blocked" the command and offered three workarounds, the first being to type `! rm -rf build` myself. That's reasonable, but it's a reminder to write the deny text as an instruction, and that the guard covers Claude's tool calls, not you.

![A dialog headed Blast Radius, rm -rf, for the command rm -rf build, saying it would delete 9 files of 498 KB, listing each file under build, with Proceed and Cancel buttons](https://blog.bokvi.com/_astro/blast-radius-dialog.0Nv-hArb_XeBRU.webp)

*Blast Radius, another demo from the launch video, goes further and lists exactly what `rm -rf build` would delete before it asks. Frame from ClaudeDevs's Claude Mods launch video.*

Blast Radius is a better design than mine, and it shows why a mod beats a deny rule here. Before it asks, it goes and finds the facts that should decide the answer. For rules that depend on the moment, such as no `git push` while you're on `main`, use the [`tool.check`](https://code.claude.com/docs/en/plugins/mods/events#approve-or-refuse-a-tool-call-before-the-user-is-asked) event instead. It fires after your permission rules have decided and lets you change that decision.

## 3. Put the number you keep checking above the prompt

The band above the prompt is the best real estate a mod gets: always visible, one line tall, and no tokens spent to fill it. A `ui.render` hook on the `AbovePrompt` site draws it. This one keeps the last request's token counts from `turn.step` and draws them:

```js
// hooks/register.js
let usage = null

export function register(on) {
  // Each request to the model: let it stream, then keep its token counts
  on('turn.step', async function* ($, e, next) {
    const result = yield* next(e)
    if (!e.agentId && result.usage) {
      usage = result.usage
      $.ui.invalidate('ui.render')
    }
    return result
  })

  // The band above the prompt: one dim line, once there is something to show
  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
    if (!usage) return next(e)
    const { Text } = $.ui.resolve(e)
    const sent = usage.input_tokens + usage.cache_read_input_tokens + usage.cache_creation_input_tokens
    const cached = Math.round((100 * usage.cache_read_input_tokens) / sent)
    return Text({ dimColor: true, children: [`last request: ${(sent / 1000).toFixed(1)}k tokens, ${cached}% from cache`] })
  })
}
```

![The same Claude Code session after the refusal: Claude's answer offering three ways to delete the folder, and above the prompt a dim band reading last request: 37.1k tokens, 99% from cache](https://blog.bokvi.com/_astro/context-band-after-refusal.Bc9RKjmi_Z9oM7b.webp)

*The band in the same session, after the refusal. The first request of the turn read 0% from cache, and the second, 99%.*

Two rules make the band behave. Every mod shares it, and the tree you return replaces whatever the mods after yours draw, so put `await next(e)` among your children if you want to keep theirs. And `turn.step` also fires for subagents' requests, which is why the hook checks `e.agentId`.

The community found far better uses for the space than mine. [cc-pr-tracker](https://github.com/sezaakgun/cc-pr-tracker) gives each watched pull request one line with its merge state, review and required checks, plus a toast when one changes. [cctop](https://github.com/tomstagl/cctop) is a btop-style pane with context fill, cost, cache hit ratio and rate limits. [Mindful-Claude](https://github.com/halluton/Mindful-Claude) runs a guided breathing exercise in the band while a turn runs. And the most-reacted demo in the design thread was [cc-arcade](https://github.com/sezaakgun/cc-arcade), which Boris Cherny [shared on X](https://x.com/bcherny/status/2099551291601248485) with "Someone already built a Tetris-in-Claude mod 🤯".

![Claude Code with the /arcade tetris command run: above the prompt, a Tetris board mid-game with a score of 188, buttons for games and close, and a status line saying the game is paused](https://blog.bokvi.com/_astro/cc-arcade-tetris.BpCl-WGj_Z1OoiX2.webp)

*cc-arcade by Seza Akgün, running in my terminal. Nine games live above the prompt and pause when Claude finishes its turn, and a pet grows from the tests, commits and edits Claude makes.*

I wouldn't play Tetris on a review, but cc-arcade is the best tutorial I found: it uses the band, a command, a `Client` module per board for animation, `$.store` for high scores and `turn.complete` to pause, all in [one repo](https://github.com/sezaakgun/cc-arcade) under MIT. Read it before you build anything interactive.

## 4. Read a mod's footprint before you install it

A mod is code that runs with your permissions, and it is not sandboxed. Per the [overview](https://code.claude.com/docs/en/plugins/mods#what-a-mod-can-reach), once one loads it can read and write your files, read your environment variables and settings, see every prompt, rewrite tool calls, approve one before you're asked, and call a model on your plan. The permission prompt is the one part of the interface it cannot redraw.

What makes this manageable is the API design. A mod has no ambient access, so it can't `import fs`. Everything goes through `$`, which means Claude Code can tell you what a mod will touch without running it:

```sh
claude plugin validate ./some-mod
```

![Terminal output of claude plugin validate for two mods: cc-arcade hooks seven events and calls only clock, command, store and ui methods; cctop hooks nine events and calls process.run, fs.write and prompt.fill among many others](https://blog.bokvi.com/_astro/plugin-validate-footprints.B7zMHSE8_Z1vCtLk.webp)

*Two footprints, both in line with what the mods say they do. cc-arcade only draws and keeps scores. cctop runs its own `cctop` binary and writes a marker file under `~/.cctop`, which is how it gets its numbers. I ran both on copies of the plugin folders without their marketplace manifests, for the reason below, and cropped the path lines.*

Read the `calls:` line before anything else. `$.process.run`, `$.fs.write`, `$.http.fetch` and `$.prompt.submit` are the ones that should match what the README promises. A PR tracker has to run `gh`; a breathing exercise has no business making network requests. Karan Bansal turned this into a [nightly scan](https://github.com/karanb192/awesome-claude-code-mods) of the mods it can find on GitHub. On September 17 it counted 72 mods, of which 30 run host processes and 14 reach the network, and as he [notes in the thread](https://github.com/anthropics/claude-code/issues/91870#issuecomment-5676126263), "the footprint is capability, not evidence of misuse".

One trap I hit: many mod repos double as their own marketplace, and `claude plugin validate .` in such a repo checks the `marketplace.json` and says nothing about the hooks. Point it at the plugin's manifest instead, as in `claude plugin validate cc-arcade/.claude-plugin/plugin.json`, or at the subfolder that `marketplace.json` names as the plugin's `source`, such as cctop's `./plugin`. If you run Claude Code for a team, managed settings have an [`allowManagedModsOnly`](https://code.claude.com/docs/en/plugins/mods/admin) option that refuses every mod a person installs, while the organization's own mods keep loading.

## 5. Copy from the four mods Anthropic ships

The [`mods` folder](https://github.com/anthropics/claude-code/tree/main/mods) is the best documentation of what good looks like, because these are the mods built into your Claude Code. `diff` is the `/diff` pane, with buttons bound to keyboard actions and scrolling it handles itself. `agents-md` is the AGENTS.md support Thariq [announced on X](https://x.com/trq212/status/2101009392611278961) on September 18. It's now a mod with a `userConfig` option that `/config` shows as "Project instructions". `sec-default` is the guard that keeps an organization's hooks, settings and deny rules out of reach of the mods a person installs, and `telemetry` shows how one mod adds a method to `$` for others to call.

Every one of them ships with tests, and you can run them without a session, a sign-in or the network:

```sh
claude --plugin-dir mods/diff   # run Anthropic's diff mod from source
claude plugin test mods/diff    # 210 tests across 30 files, 6.9 s on my machine
```

A test gets the engine's real `$` and its own `on`. The hooks it registers sit beneath the mod and stand in for the world: tools, git, the clock. So you can raise a tool call, answer it, and check what the mod did. Write tests like that for any mod that guards something. A guard you've only tried by hand is a guard you've seen work once.

When you write against the API, trust the types your own build writes over anything online, this post included. Each time Claude Code loads a mod from `--plugin-dir`, it writes `.d.ts` files for your exact version into the mod's `.claude-plugin/types/`. The copy published in the repo, [`mods/types/claude-code.d.ts`](https://github.com/anthropics/claude-code/blob/main/mods/types/claude-code.d.ts), says on its first line that Claude Code 2.1.277 wrote it. I tested on 2.1.287.

## Where I'd start

Mods are the most capable extension point Claude Code has had and the least contained, which is a reason to start small rather than to stay away. Before you write one, check the [comparison table](https://code.claude.com/docs/en/plugins/mods#compare-mods-settings-hooks-skills-and-mcp-servers): if a skill, a settings hook or an MCP server does the job, it's simpler to share and easier to trust. When none of them can, because you want something drawn, a call held, or a command that runs without spending a turn, ask Claude for the smallest mod that does it, read its footprint, and give it a test. If you're coming from [dynamic workflows](https://blog.bokvi.com/blog/claude-code-dynamic-workflows/), it's the same idea pointed at the interface: Claude writes the code that changes how Claude Code works. More on the tool lives under [Claude Code](https://blog.bokvi.com/tags/claude-code/).