I wanted one thing: to see my context window without typing /context.
Not a dashboard. Not a plugin I install and configure for twenty minutes. Just a small bar above the prompt that tells me how full the window is, the way the /context command does, but always on screen. One sentence of a prompt later, it was there.
This is the story of the first Claude Code mod I built without writing a single line of code. Claude wrote it, validated it, tested it, and hot-reloaded it into my session. I just watched.
What a mod actually is
Claude Code mods (shipped in v2.1.287, on by default) are small JavaScript or TypeScript modules that live inside a plugin. They see every event in your session as it happens: tool calls, the prompt you submit, turns starting and ending, and every piece of the interface as it is drawn.
That last part is the new bit. Mods can rewrite what Claude Code does and draw their own UI: a pane, a band above the prompt, a status line entry, a toast. Everything the engine draws is a component you can hook into.
And here is the part that still feels strange: you do not need to learn the API to try one. You describe the mod you want, and Claude Code builds it.
Step 1: ask for it
Open any project, run claude, and paste a prompt like this:
Create a mod that draws my context window as a stacked bar above the prompt, one colour per category like /context, toggled with /context-bar.
Claude Code loads its own plugin-authoring skill, reads the engine's type declarations, and writes three or four files into a session folder under ~/.claude/dev-mods/. Then it asks one question: allow hot reloading for this session?
Say yes. When the turn ends, the bar appears above your prompt.
That is the moment that sold me. I did not open an editor. I did not read the docs. I described a UI in plain English, and it showed up.
Step 2: make Claude validate its own homework
Before I even looked at the code, I asked for two things while the session was still warm:
-
Run
claude plugin validateon the folder. The validator reads the module the way the engine will and names anything the engine would refuse. Think of it as a compiler for mods: non-negotiable. -
Write a
tests/file and runclaude plugin test. That exercises the hooks against the real engine. No TypeScript toolchain needed on your machine.
This is the difference between "Claude wrote something plausible" and "the engine will actually load this". An LLM will happily produce a mod that looks right and fails at load time. Validate and test are what separate the magic from the garbage.
Step 3: peek under the hood (optional, but worth it)
Curiosity won, so I read the code. A mod is one module exporting register:
export function register(on) {
on("ui.render", { component: "AbovePrompt" }, ($, e, next) => {
const { Box, Text } = $.ui.resolve(e);
return Box({
paddingX: 1,
children: [Text({ children: "hello from my mod" })],
});
});
}
The shape is middleware. Your hook runs, then next(e) hands the event to the next plugin in the chain, then to Claude Code itself. Every hook does one of three moves:
-
Observe: call
next(e), then look at the result. Record every file edit, take a reading after each turn. -
Rewrite: call
nextwith a modified event. Change what the rest of the chain sees. -
Answer: skip
nextentirely and serve the event yourself. Refuse a tool call, handle a slash command.
One detail I stole from Addy Osmani's guide: do not keep history in a module-level let. Hot reload is a fresh load, register runs again, and module variables start over. Keep readings in $.state, declared in a small types contract, and they survive reloads. If you skip the contract, claude plugin validate stops you with an error that names the fix. The tooling here is genuinely good.
Step 4: make it permanent
The session folder dies with the session. Copy the mod somewhere stable:
mkdir -p ~/.claude/mods
cp -R ~/.claude/dev-mods/<session-id>/context-bar ~/.claude/mods/context-bar
Keep the manifest in .claude-plugin/plugin.json, the hooks/ folder with its module, and the types/ contract if the mod keeps state. Copy tests/ too, so you can rerun the tests after an update.
Then tell Claude Code where it lives. The one place that reaches every session, including the ones the desktop app starts, is the env block of your user settings file. Open ~/.claude/settings.json and add:
{
"env": {
"CLAUDE_CODE_PLUGIN_DIRS": "/Users/you/.claude/mods/context-bar"
}
}
Use the full path, not ~. Several mods are separated with a colon. For a one-off terminal session, claude --plugin-dir ~/.claude/mods/context-bar does the same without touching settings.
Open a new session in any project. The bar is above the prompt before you type anything, and /context-bar hides it.
Two things that bit me
Early access means churn. The API can change between releases, so a mod written today may need a touch-up after an update. Rerun claude plugin validate after every upgrade. It is cheap insurance.
Check which claude you are running. The desktop app bundles its own engine, and the one on your PATH may be older. Mine was, and its validator did not recognize the hooks module at all. Validate with the app's bundled binary, or update the CLI first. This cost me twenty confused minutes staring at an error that was not mine.
Why this feels different
I have used hooks, slash commands, and skills. A settings hook runs a shell command per event and passes JSON over stdin and stdout. A mod loads once, keeps state, draws UI that updates as events happen, and can call back into Claude Code: open a pane, run a process, register a slash command, register a tool the model can call. Some of Claude Code's own features are built as mods, including AGENTS.md support and the /diff pane. Their source is public, so you can read how the team builds them.
But the real shift is the loop. The skill for writing mods ships inside Claude Code, so the agent modifies its own runtime, in the same session, and hot reload shows you the result on the next turn. I kept asking for tweaks ("add a legend line with per-category token counts", "show a toast when context crosses 85%") and watched the bar change. It felt less like configuring a tool and more like pair programming with the tool itself.
If you could describe a mod in one sentence, what would it do?
Top comments (2)
Hot-reloading a UI you only described in one sentence is a wild loop, and leaning on validate and test to separate plausible from actually loadable makes a lot of sense to me. The PATH mismatch would have got me too.
The observe / rewrite / answer taxonomy is the useful part, and it also ranks the blast radius. Observe can only lose your display, rewrite changes what the engine sees, answer can quietly disable built-in behavior. So an observe mod like your context bar is the right first project, and the tweak loop stays in the safe tier.
One addition to the churn section. A mod that fails to load after an upgrade throws no banner. The UI just never appears and everything else works fine. I have had a tool report success while the live artifact stayed empty, so after every upgrade I open one session and confirm the bar still draws.