Claude Mods

Claude Code’s extension model is changing shape: from a set of independent hooks that vote on an operation, to an ordered stack of mods that wrap it. That one change is why Claude Code is about to have an admin story it has never had — ordering creates precedence, and precedence is what authority is made of. The machinery is already in the binary on your machine, behind a flag, with an API that may change without notice.

9 modules
2 interactive tools
22 quiz questions
~40 min
14 Sep 2026

Primary sources, read in full: issue #91870 (body + 168 comments) · the three built-in mods’ source · hooks docs · plugins docs · the v2.1.270 changelog and binary

Before you start: the one thing that changes what you should do

Claude Mods are not shipped and not stable. The machinery is in the CLI you already have, behind CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1, and the README that ships with it says the API “may change between releases without notice” — against a release cadence of roughly one a day. So this course marks exactly one distinction, and it is the only one you need: stable means the plugins and hooks surface that is documented today and that you can build a team on; unstable means the mods surface, which you should experiment with and should not depend on.

Everything else here is about the architecture, because the architecture is the part that will survive the renaming. Mods replace a set of independent, parallel hook handlers with an ordered stack of wrappers — and almost everything interesting about them, including the first credible admin story Claude Code has had, falls out of that single change.

Short on time? Module 3 for the design decision that explains everything else, and Module 8 to find out whether it stuck.

Course Modules

  1. What a mod isStart here
  2. The baseline: what ships todayDocumented
  3. Parallel handlers become nested middlewareThe core
  4. The tier model and who outranks whomGovernance
  5. The three built-in mods, read as evidenceSource
  6. What is new versus what is renamedPrecision
  7. Which mechanism, for which jobInteractive
  8. Voter, wrapper, or positionInteractive
  9. CounterargumentsBoth sides
1

What a mod is

A plugin whose behaviour wraps the engine instead of voting on it
By the end of this module you will
  • Be able to say what a mod is in one sentence, and what makes it different from a hook
  • Know the one status fact that decides whether you should build on this yet
  • Understand the question the rest of the course answers: what ordering buys you

The definition, from the README that ships with them

“A mod is a Claude Code plugin whose behaviour lives in a hooks module: one register(on, options) entry that hooks the engine’s events as functions ($, e, next).” anthropics/claude-code, mods/README.md

Three things are packed into that. A mod is a plugin — it installs the way plugins already install. Its behaviour lives in one module with one entry point, rather than in a scattering of shell commands wired to event names. And its handlers take next, which is the entire story: a handler receives the rest of the system as a callable and decides what to do with it.

Voter versus wrapper, in one comparison

Today’s hooks are voters. Each one is handed an event, runs independently of the others, and returns a verdict: allow, deny, or say something. They do not see each other, they have no defined order, and none of them is around the operation — they are beside it.

A mod is a wrapper. It is handed the event and a continuation. Run code, call next, and the rest of the system proceeds inside your call. Which means you get the thing a voter can never have: the return journey. You are still on the stack when the result comes back, so you can read it, change it, or decline to have made the call at all.

Why that one change carries so much

A set of voters has no inside and no outside. Two plugins that both want to filter the same tool call are peers: whichever the runtime happens to invoke first wins, and neither can rely on that. There is no way to express “my rule applies to yours”, so there is no way to express organisational authority — which is why Claude Code has had plugins for a long time without having an admin story worth the name.

An ordered stack of wrappers has an inside and an outside, and that is a structural fact rather than a policy: whatever registers first wraps everything after it. Once that is true, four things become possible that no amount of policy language could deliver on top of voters:

What ordering buysWhy a voter cannot do it
Transform a result on the way backA voter’s decision is made before the operation runs. It never sees what came back in the same breath as the decision it made about it
Guarantee you run before another pluginVoters are unordered peers. “Run first” is not expressible
Observe everything, including other plugins’ effectsA voter only sees the events it subscribed to. It is never outside another plugin, so another plugin’s calls are invisible to it
Remove an affordance so nothing beneath can use itA voter can refuse to do a thing. It cannot take the ability to do that thing away from its peers

That last one is the sharpest, and it is worth holding on to now because it recurs through the rest of the course. Because every side effect a mod may perform arrives through an object it is handed, an outer mod can hand the inner ones a version of that object with a capability missing. The inner mod is not blocked from doing the thing; it has no way to express the thing. That is capability removal by construction, not by policy checking — and it is the difference between a rule and a wall.

The status, once, because it decides what you do next

Stable versus unstable, and nothing in between

Stable: the plugins and hooks surface documented today. Build on it, standardise on it, put it in front of a team.

Unstable: mods. The machinery is in the v2.1.270 CLI — the flag CLAUDE_CODE_ENABLE_FUNCTION_HOOKS, along with plugin-types, engine.create, next.to and sec-default, are literal strings in the shipped binary — and three mods written against it ship inside the CLI. It is absent from the documentation and named nowhere in the v2.1.267–270 changelogs. The README is explicit: hooks modules “load only where function hooks are enabled, and the API these mods are written against may change between releases without notice.”

That sentence is the whole risk calculus. “Without notice” is not boilerplate when four releases went out in the four days around the feature’s renaming. Run it, read it, feed back on it — and treat anything you build on it as something you have volunteered to maintain against a moving target.

Two opposite misreadings circulate and both cost you something. “Mods have shipped” will surprise you with a breaking change on a Tuesday. “It is just a discussion” is the more expensive error: the code is in the binary you already have, the design is being argued out in public right now, and treating it as vapour means learning the shape of it six months after you could have influenced it.

Takeaways
  • A mod is a plugin whose behaviour is one module, one register, handlers as ($, e, next)
  • Hooks vote beside an operation; mods wrap around it — which buys the return journey
  • Ordering gives the system an inside and an outside, and that is what makes authority expressible
  • Four things wrapping buys: transform the result, guarantee precedence, observe everything, remove an affordance
  • One status distinction: stable = plugins and hooks today; unstable = mods, API may change without notice
2

The baseline: what ships today

You cannot judge what mods add without knowing what already exists
By the end of this module you will
  • Know the six extension mechanisms Claude Code documents today and what each is for
  • Be able to say what today’s hooks can and cannot do

A plugin is a directory with an optional .claude-plugin/plugin.json manifest that can bundle any of:

DirectoryWhat it holds
skills/Model-invoked capabilities as SKILL.md folders; namespaced /plugin:skill
agents/Custom subagent definitions
hooks/hooks.jsonEvent handlers
.mcp.jsonMCP server configuration
.lsp.jsonLanguage servers for code intelligence
monitors/Background watchers that notify Claude as events arrive
bin/, settings.jsonExecutables on the Bash PATH; default settings when enabled

Today’s hooks, precisely

Hooks today are configured as JSON with a matcher, and each handler is one of five types: command (a shell command receiving JSON on stdin), http, mcp_tool, prompt, and agent (experimental). They fire on a long list of lifecycle events — PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, PermissionRequest, SubagentStop and many more — and they decide by exit code or by emitting JSON.

The three properties that matter for the comparison
  1. They run in parallel. When several handlers match an event, they all run. There is no ordering relationship between them.
  2. They decide, they do not wrap. A hook can allow, deny, or add context. It cannot run around the operation, see the result, and transform it.
  3. They are processes or endpoints. A command hook spawns a subprocess and talks JSON over stdin. There is no shared type system between the hook and the thing it hooks.

Hold property 1 especially. Almost everything mods change follows from replacing it.

Takeaways
  • Plugins already bundle skills, agents, hooks, MCP, LSP, monitors, binaries and settings
  • Today’s hooks are five types, JSON-configured, and run in parallel
  • They decide (allow / deny / add context); they do not wrap execution
  • No shared type system — a command hook is a subprocess speaking JSON
3

Parallel handlers become nested middleware

One design decision. Everything else in the proposal is a consequence of it.
By the end of this module you will
  • Be able to explain the ($, e, next) signature and what each part is for
  • Understand why nesting, not TypeScript, is the real change
  • Know what the $ object is and why side-effect tracking is the safety story

The shape

The author’s own summary: “Function Hooks let you modify CC very deeply, while still being safe through side-effect tracking over a parameterized $ object, and while composing neatly using a registration-order-based ‘next’ continuation model a la Express, or Koa.”

From the mods README, the shipped description: “A mod is a Claude Code plugin whose behaviour lives in a hooks module: one register(on, options) entry that hooks the engine’s events as functions ($, e, next).”

The one sentence to take away

From the advanced series: “Plugins nest like middleware. The first one registered wraps the rest, so admins prepend for control and append for defaults.”

TypeScript and LSP support are the headline, and they are the least interesting part. The change that matters is that extension moves from a set of independent voters to an ordered stack of wrappers. Ordering creates precedence; precedence creates the possibility of authority; authority is what makes an admin story possible at all. Module 4 is that consequence.

What wrapping buys that voting cannot

CapabilityToday’s hooksFunction hooks
Deny an operationYesYes
See the result and transform it before the caller reads itOnly via a separate Post event, with no link to the Pre decisionYes — you are around the call
Replace the operation with your ownNoYes — return a value instead of calling next
Guarantee you run before another pluginNo — parallelYes — registration order is nesting order
Observe every event including other plugins’ callsNoYes — a hook on *
Change what is renderedNoYes — hook components, modify props or wrap render nodes

On the wildcard: “A single hook on * sees every event, including every plugin’s own calls on $, so an audit log is one function.” For anyone operating under an audit regime that is the most consequential line in the entire proposal, and Module 5 shows it already has a shipped consumer.

Takeaways
  • ($, e, next): the effect interface, the typed event, the continuation
  • Registration order is nesting order — “admins prepend for control and append for defaults”
  • Wrapping buys result transformation, operation replacement, guaranteed precedence, wildcard observation and render control
  • Effects route through $, so an outer hook can remove an affordance from everything beneath
4

The tier model and who outranks whom

The governance consequence of ordering — and it is already implemented
By the end of this module you will
  • Know the three tiers and which way authority runs
  • Be able to explain what an organisation can enforce that it could not before
  • Know the failure mode the shipped implementation chose, and why it matters

This is not speculation about a future admin story. The sec-default mod ships inside the CLI and its README states the model plainly.

prepend
Organisation, outermost. Wraps everything. sec-default is seated here on a machine with managed settings, or for a Team or Enterprise organisation, “unless managed prependPlugins says otherwise.”
user
The plugins a person installs. Sits beneath the organisation’s prepend tier and above its append tier. Everything a user adds lands here.
append
Organisation defaults, innermost. Runs last, closest to the engine — defaults a user’s plugins may legitimately sit above and modify.

What sec-default actually does

Its README is unusually precise about scope: it “keeps an organization’s classic hooks, prompt content, managed settings and tool policy out of reach of the plugins a person installs; adds no policy of its own. Everything else passes through untouched.”

Three moves and nothing else: continue past the user tier (next.to(e, "append")), refuse a user-tier caller by name ({ deny } when next.origin.tier is user), or pass (next(e)). What it protects: classic.* events, prompt.section / prompt.context / skill.prompt / attribution.text, settings.read, and tool.describe / command.describe / agent.offer / agent.spawn.

The sentence an auditor would care about

“A subject’s provenance is the event’s pinned e.provider; policy is read through $.settings.read({ source: "policy" }), one read serving a burst; both fail closed, so an unreadable policy counts as a policy in force.”

Fail-closed on unreadable policy is the correct default and it is worth noticing that it was chosen. A system that fails open when it cannot read its own rules is one that can be disabled by breaking it.

The honest framing of what changed

The README says it directly: “Some of what an organization sets today (its classic hooks, its managed CLAUDE.md and rules, its settings, its MCP allowlist) was never within a person’s reach before function hooks.”

Read that carefully, because it inverts the usual reading. sec-default is not new power for administrators. It is a patch for power that function hooks would otherwise hand to users — the ability to reach org settings that were previously out of reach simply because no extension mechanism could touch them. The org tier restores the status quo; it does not extend it. Anyone selling mods to an organisation as “new control” has the direction backwards.

Takeaways
  • Three tiers: prepend (org, outermost) → user → append (org, innermost)
  • sec-default ships in the binary, seated outermost for managed/Team/Enterprise machines
  • Three moves: continue past user, deny a user-tier caller, or pass. No policy of its own
  • Fails closed on unreadable policy
  • It restores isolation that function hooks would otherwise remove — not new admin power
5

The three built-in mods, read as evidence

What Anthropic chose to build first tells you what they think this is for
By the end of this module you will
  • Know what each built-in mod does and what its existence signals
  • Be able to use the source as the most reliable available spec

Three mods ship inside Claude Code, with source published at mods/: sec-default, diff and telemetry. The README calls this folder “their source, published as it is built into the binary.”

ModWhat it doesWhat its existence tells you
sec-default Keeps org settings out of reach of user plugins. Seated outermost on managed / Team / Enterprise machines. They built the containment before the capability was public. The org tier is not an afterthought.
diff /diff: the session’s uncommitted changes in a pane beside the transcript, refreshed as Claude edits files. A first-party user-facing feature implemented as a mod — the strongest signal that this is intended as the real extension path, not a side door.
telemetry Adds $.telemetry (log, mark) so a plugin can record a first-party analytics row. Sends nothing wherever Claude Code’s analytics are off. Demonstrates noun composition: one mod extends the $ every mod above it receives.
The migration intent, stated

“Our intent is to take further extant features as they exist in CC today and migrate them to mod form.”

That is the most load-bearing sentence for anyone deciding how seriously to take this. A vendor dogfooding its own extension API by rebuilding shipped features on it is the strongest available commitment signal — stronger than a roadmap, because it creates internal cost if the API is bad.

Composition: the noun contract

A mod that adds a noun to $ owns that noun’s types in a single declaration file, and “the contract is the only declaration of the noun… so the implementation cannot drift from what callers read.” A mod calling another’s noun reads the same file and never copies it. For a plugin outside the repo, the README says you point your tsconfig at that mod’s types/ folder for now — “once the engine writes the contracts of the plugins a session has installed, /plugin-types will put them beside claude-code.d.ts and the include goes away.” That mechanism does not exist yet.

There is also a real test harness: claude plugin test mods/diff, with a kit providing tier(), mock.clock/env/store and $.ui.press. A testing story this developed at early-access stage is unusual and is itself evidence of seriousness.

Takeaways
  • sec-default, diff, telemetry ship in the binary with published source
  • Containment (sec-default) was built before the capability went public
  • Stated intent to migrate existing features to mod form — the strongest commitment signal available
  • Noun contracts let mods extend $ for mods above them without type drift
  • A real test harness exists; the external type-distribution story does not yet
6

What is new versus what is renamed

A discipline for reading any feature announcement, applied here
By the end of this module you will
  • Be able to separate genuinely new capability from relabelled existing capability
  • Know the one sentence that defuses most of the confusion
The defusing sentence

“A mod is just a plugin that uses function hooks, nothing is changing there. Hopefully the ontology is not too confusing.”

So “mod” is not a seventh plugin component alongside skills, agents and MCP servers. It is a name for a plugin that happens to contain a hooks module. Distribution, manifests, marketplaces, install flow — all unchanged. If you already understand plugins, you already understand 80% of mods.

Claim you will hearWhat is actually true
“Mods are a new plugin type.” Renamed A mod is a plugin. The new thing is the hooks module inside it.
“Mods let plugins deny tool calls.” Already shipped Today’s PreToolUse hooks deny tool calls. Mods change how and add ordering, not the denial itself.
“Mods let plugins add slash commands / skills / agents.” Already shipped Plugins do all of this today.
“Mods let plugins draw UI.” Genuinely new Hooking React components, modifying props, wrapping render nodes. Nothing today does this.
“Mods give a guaranteed audit log.” Genuinely new A hook on * seeing every event including other plugins’ $ calls has no equivalent today.
“Mods let admins constrain what plugins can do.” New mechanism, restoring old isolation See Module 4 — capability removal from $ is new; what it protects was previously unreachable anyway.
“Mods can orchestrate other agents.” Unresolved Raised in the thread and not settled — see objection 3 in Module 9.
Takeaways
  • “A mod is just a plugin that uses function hooks” — the plugin system is unchanged
  • Genuinely new: render control, wildcard event observation, capability removal from $, guaranteed ordering
  • Not new: denial, skills, agents, MCP, marketplaces — all shipped today
  • Unresolved: whether a mod can drive orchestration rather than only intercept it
7

Which mechanism, for which job

The decision you actually face — and most of the time the answer is not “a mod”
By the end of this module you will
  • Reach for the cheapest mechanism that solves the problem rather than the newest
  • Know the small set of jobs that genuinely require a mod

A new extension point creates a gravitational pull toward using it for everything. Resist it. The ladder below runs cheapest and most stable first; go down a rung only when the one above genuinely cannot do the job.

RungUse whenStability
CLAUDE.md / settingsYou want different default behaviour or context. No code.Stable
SkillA repeatable procedure you want Claude to follow or invoke by name.Stable
Classic hookDeterministic allow/deny/notify at a lifecycle point, independent of other handlers.Stable
SubagentIsolated context with its own tools and prompt for a sub-task.Stable
MCP serverNew tools or data from an external system.Stable
ModYou need to wrap execution, guarantee ordering against other plugins, observe everything, draw UI, or remove a capability from plugins beneath you.Unstable

Ten problems. For each, pick the cheapest mechanism that actually solves it.

Tool 1

Cheapest mechanism that does the job

0 / 10
Result

Takeaways
  • Ladder: CLAUDE.md → skill → classic hook → subagent → MCP → mod
  • A mod is justified by wrapping, ordering, observation, rendering, capability removal — and little else
  • Everything above the last rung is documented and stable; the last rung is neither
8

Voter, wrapper, or position

The architecture, scored — and the third category is where it gets interesting
By the end of this module you will
  • Reliably tell what needs wrapping from what a parallel handler could already do
  • Recognise the smaller set of things that need wrapping and a particular position in the stack

Ten goals. For each, decide what the architecture has to give you:

  • A voter can do it — today’s parallel hooks are sufficient. No nesting required.
  • Needs wrapping — it requires being around the operation, with a continuation you may call, transform the result of, or decline to call. Order does not matter.
  • Needs wrapping and position — wrapping alone is not enough; it only works if you are at a particular place in the stack. These are the ones that make an admin story possible, and they are the reason ordering is the architecture rather than a detail.
Tool 2

What does this actually require?

0 / 10
Result

Takeaways
  • Most useful things need wrapping; a smaller, more powerful set needs wrapping and position
  • Wrapping puts you outside the operation; position puts you outside the other mods
  • Prepend for control, append for defaults — the same mechanism at opposite ends
  • The admin story lives entirely in the third category, which is why ordering is the architecture
9

Counterarguments

Marked as objections. None is the view of the person it is aimed at.
By the end of this module you will
  • Be able to argue against the design on architectural grounds rather than taste
  • Know which objections the author engaged and which remain open
Ground rules

Objections 1–3 are quoted from named commenters in the public thread and are their arguments, not mine and not Anthropic’s. Objections 4–6 are the course’s own and are marked . Where the author replied in the thread, his reply is summarised.

Objection 1 — raised by @serejke

The extension API becomes a public ABI, and the debugging story gets hard

“This starts to feel like a closed-source platform trying to expose enough hooks to be infinitely extensible. At some point the extension API becomes almost as complex as the runtime itself. If plugins can intercept tool calls, filesystem access, UI rendering, engine creation, capabilities, and continuation flow, then all of those things effectively become public ABI. Internal refactors now have compatibility implications.”

And on debugging: “With several middleware layers modifying events, replacing execution, calling next() multiple times, handling cancellation, etc., a bug can come from the core, plugin ordering, another plugin, capability wrapping, or some interaction between them.” He proposes an alternative: open-source the runtime, keep a smaller stable plugin API, and let people fork when they want to change execution semantics.

What bears on it: This is the strongest objection in the thread and it is not really answerable at this stage — it is a prediction about maintenance cost over years. The published noun contracts and the test harness are evidence Anthropic is taking the versioning problem seriously. But “every event is now an ABI” is structurally true, and the current mitigation is the freedom to break the API, which is exactly the thing that expires when it ships.
Objection 2 — raised by @backnotprop

Middleware-only is a miss: a mod should be able to drive, not just intercept

“I think it is somewhat of a miss if these function hooks/mods only sit in the execution path as deep middleware… Ideally, a Mod could enact the general agent-runtime/orchestration API on its own (invoking messages, creating sub-agents, etc).”

What bears on it: Raised on 14 September and not answered in the thread at the time of writing. It is a real architectural fork: a purely reactive middleware model means a mod can shape what happens but cannot start anything, which rules out a whole class of orchestration extensions. The $ object may already permit more than “middleware” suggests — it exposes nouns a hook can call, not only events it can answer — but nothing in the published material settles it.
Objection 3 — the regulated-production angle, raised by @alhe99

The audit story is the justification, and it has a hard dependency

This one is a qualified endorsement rather than a rejection, which makes it more useful. Running a self-hosted fleet against payments infrastructure in PCI-DSS scope, he reports generating change-control records by reconstructing agent behaviour after the fact from artifacts, and says “$.on('*') is the difference between inferred evidence and an actual audit log. That alone would justify the feature for anyone operating under an audit regime.” He also notes a usage-limit watchdog that scans terminal panes every five minutes with byte-exact string matching “because there is no event to subscribe to when a session hits a wall… exactly as brittle as it sounds.”

Why it belongs here: It is the clearest statement of what the feature is for at organisational scale, and it comes with a dependency: an audit log built on an unstable API is an audit log that can break silently between releases. For a compliance use case that is a worse failure than not having it, because you will believe you have evidence you no longer have.
Objection 4 — the course’s own

Ordering solves precedence and creates a coordination problem

“The first one registered wraps the rest” is clean for two parties. With a dozen third-party mods installed, correctness now depends on install order, and no plugin author can know what else is in the stack. Two mods that each work perfectly can combine into a broken session, and the failure will present as a bug in whichever one is easiest to blame.

What bears on it: The author addressed ordering directly on 11 September, calling it “possibly the most fundamental question” and giving a first-order answer (plugins already have an order) and a second-order one. That it was the question he most welcomed suggests it is understood as central. It is not obviously solved — ordering problems in middleware stacks are a decades-old category and nobody has made them disappear.
Objection 5 — the course’s own

The most useful capabilities are also the most dangerous ones

Consider the demo where a plugin “replaces secrets in tool output before the model reads them,” and the one that hides values in the desktop app until you hover. Both are genuinely good. Both describe a mechanism for altering what the model sees and what the human sees, in a stack where the user may not have written most of the layers. The same primitive that redacts a secret can suppress a warning.

What bears on it: sec-default is precisely the answer for organisations — capability removal from $, seated outermost, failing closed — and the wildcard hook means an audit mod can see what every other mod did. For an individual installing community mods with no org tier above them, the protection is “install things you trust,” which is the same answer as every plugin ecosystem and has the same track record.
Objection 6 — the course’s own

Adopting early is a maintenance commitment disguised as a feature

“May change between releases without notice” against roughly daily releases means anything you build here is code you have agreed to keep repairing, on a schedule someone else sets. For a personal workflow that is a fine trade. For anything a team depends on, you have taken on an unbounded, externally-timed maintenance obligation in exchange for capability you could mostly approximate with documented mechanisms.

What bears on it: The instability is stated plainly rather than buried, the author actively solicits feedback, and early adopters demonstrably shaped the design — the rename and the commitment both came out of the thread. Early adoption buys influence. That is a real return, and it accrues to the person doing it, not to the team who inherits the code.

Two objections that do not survive the material

The objectionWhy it fails
“This is just hooks with extra steps.” Today’s hooks run in parallel and cannot wrap, replace, observe other plugins, guarantee order, or render. Four of those five have no equivalent. The mechanism is different in kind, not degree.
“Anthropic is adding this to lock people in.” The design goes the other way: source published, a documented primitive, an org tier that limits what the vendor’s own extension path can reach, and a public thread where the author said community response “likely dictates whether this ships.” Objection 1 is the disciplined version of the underlying worry, and it is about architecture rather than motive.
Takeaways
  • Strongest external objection: every event becomes public ABI, with a hard multi-layer debugging story
  • Open architectural question: can a mod drive orchestration, or only intercept it?
  • The audit capability is the clearest organisational justification — and rests on an unstable API
  • The course’s own three: ordering coordination, redaction cuts both ways, early adoption is a maintenance commitment
  • Early adoption’s real return is influence over the design, which accrues to the adopter, not the inheritor