senn-techsenn-tech
AI & Development
AI & Development2026-10-05· By Franz Senn

Claude Mods: What the New Plugin Format in Claude Code Is Allowed to Do

On 1 October 2026 Anthropic released Claude Code 2.1.287. The changelog line for it is one sentence long: "Added Claude Mods: plugins may now modify deeper behavior." The videos about the feature call it one of the biggest updates Claude Code has had. An explainer by Tristen O'Brien from 5 October walks through five mods he built in a week: a checklist view that hides the tool calls, a model and effort switch, a dock that runs a team of helper agents, a photo and video studio wired to the paid Higgsfield service, and a racing game for the waiting time. The channel marks Higgsfield as the sponsor.

For anyone who runs Claude Code on machines their department is responsible for, the interesting question is a different one. What is going to run on those laptops, who is allowed to install it, and how do you take it away again? The answer is fully documented, and it is more specific than any demo. The working conditions of the tool itself are the subject of Claude Code in Practical Use.

What a mod is technically

A mod is a plugin. The directory needs a .claude-plugin/plugin.json, a hooks/hooks.json carrying one path in its modules field, and a JavaScript or TypeScript module exporting register(on, options). Inside it, every function that should do something is registered with on against an event name, for instance on('tool.call', { tool: 'Bash' }, async ($, e, next) => next(e)). A handler receives four things: the mods API as $, holding every method, the event as deeply frozen plain data, the next handler next, and an abort signal. The shape is middleware.

Per event the handler has exactly three options. It can watch the event and pass it on, change it by handing a copy to next, or answer it itself, in which case the behavior Claude Code would have shown never happens.

ModHook in a settings fileSkillMCP server
Where it runsinside the Claude Code processas a process or HTTP requesttext in the contextits own process
Can change the interfaceyesnonono
LanguageJavaScript, TypeScriptBash, Python, anything by pathMarkdownanything
Organized byplugin manifest, hooks modulesettings filea folder with SKILL.mdMCP configuration
Reviewed withplugin validate, plugin testsettings reviewread the texttool and process list

One detail here matters more for the review than it looks. A mod only draws where drawing is possible, which means the terminal and the Code tab of the desktop app. In the chat panel of the VS Code extension and under claude -p the hooks do run but draw nothing, and under WSL plugins are not loaded at all. A mod can therefore change the model and the tool calls in an environment where nobody sees any part of it on screen.

On by default, four days old, and the changelog is honest

Mods are enabled by default from 2.1.287. The environment variable from the early-access phase is ignored from that version onward, so setting it to 0 does not keep mods off a machine.

The release pace is fast. npm lists 2.1.283 on 25 September, 2.1.287 on 1 October at 16:59 UTC, 2.1.289 on 3 October and 2.1.290 on 5 October. Two lines in the 2.1.289 fixes are worth reading as an operator. The first: "Fixed a deny or ask rule on a nested part of a compound shell command not holding over a user-installed mod's approval on managed machines." In plain terms, in 2.1.287 and 2.1.288 a deny or ask rule did not hold against a user-installed mod's approval on a managed machine. The second: a user-installed plugin could rewrite the descriptions of the sign-in tools belonging to an organization-managed MCP server, which is the text a user reads while authenticating.

Both were fixed four days after the feature shipped. Whoever deployed 2.1.287 had that hole, and the fix is version-bound. That is the argument for putting the installed CLI version into the same inventory sheet as the container images.

What a mod is allowed to do

The docs do not soften it: "A mod is code that runs with your permissions." What they explicitly grant a mod:

  • It acts with the user's permissions, reads and writes files wherever the user can, starts processes and reaches the network.
  • It reads secrets, because it sees environment variables and the settings files, up to and including the API key.
  • It sees the session, every prompt, every model answer, every tool call.
  • It changes the session by rewriting prompts and tool calls, submitting a prompt as though the user had typed it, routing a request to a different model, or messaging another session.
  • It can approve a tool call before the user is ever asked.
  • It spends usage, because it may call a model on the user's plan or API key.

"Not sandboxed" is its own paragraph, with a qualifier that gets skipped often. The Claude Code sandbox isolates Claude's own Bash commands. A process a mod starts through $.process.run runs outside that sandbox.

The sharpest sentence in the documentation is aimed at everyone who has already built permission rules. A denied Read(.env) does not protect the file, because a mod can read the same file with $.fs.read. Denied deny rules and managed PreToolUse hooks do take precedence over a mod's approval, but only for tool calls made by Claude itself, not for the mod's own $.fs and $.process calls. A mod may also approve a request that an ask rule would have put to the user, or one that a PreToolUse hook from unmanaged settings blocked. In auto mode a request approved that way continues without the command classifier. The docs name one limit as well: a mod can restyle much of the interface, but not the permission prompt. Its choices and its text stay untouched.

Four steps, the first three of which execute nothingclaude plugin validateread hooks: and calls:check the regimesfs, process, model, networkset the guard optionallowManagedModsOnlypolicy mod with .catchotherwise fail-open
The static review path from the docs. The inventory is dependable; the claim about the process behind it is not. (Quelle: senn-tech, own reading of the Claude Code docs (05 Oct 2026))

The guard, and the trap in your own policy mod

On a machine with managed settings, and for users signed in with a Team or Enterprise plan, Claude Code loads a built-in mod called sec-default@builtin, ahead of every mod a user brings. It sits at a higher priority tier, it can refuse other mods, and it implements two options. allowManagedModsOnly set to true keeps every mod the user brings from loading, including one Claude wrote during a session. allowModsToOverrideDenyRules set to false stops a mod from approving a request that violates a deny rule or a managed PreToolUse hook.

Where that guard stops is stated in half a sentence and decides a great deal. When authentication happens with an API key, or through Amazon Bedrock, Google Cloud Agent Platform or Microsoft Foundry, the guard loads only if managed settings exist on the device at all. An organization that ships no managed settings has no guard, regardless of what individual users put in their own files.

Anyone who wants to build a policy mod that checks other mods gets a 14-line pattern in the docs. The catch sits in the same section and is one of the few places where Claude Code leaves a security behavior open by default. A hook that throws passes the event on to the next handler. A checker mod that crashes therefore lets through exactly what it was supposed to stop. Every policy mod needs a .catch handler carrying a veto:

module.exports.register = function register(on) {
  on('plugin.register', { pluginId: '*' }, blockUnauditedProcess({ config: CONFIG }))
    .catch((err, $, e) => ({ Veto: `checker failed: ${e.message}` }));
};

Two further levers remain. disableSideloadFlags set to true rejects --plugin-dir and --plugin-url, so a user cannot load anything in the first place. disableAllHooks set to true deactivates all hook management for a running session, including the settings-file hooks that have nothing to do with mods.

Reading a mod before installing it

claude plugin validate <directory> executes nothing and still prints two lists. Under hooks: it names the events the module handles, and under calls: every mods API call it makes, including calls from a function registered in a registry. --strict turns warnings into errors, --json produces a report a pipeline can consume, and the same command covers a whole folder of plugins. claude plugin test runs the mod's .test.ts files and exits with status 1 when one fails.

That list is an inventory rather than a proof. It does not say what a listed $.process.run call then does to the machine. We also read why the list can be trusted. The module runs in a container of its own with no Node access, no fs, Buffer or process, and no dynamic import(), and Claude Code refuses to load a mod whose use of the mods API this command cannot read. A mod that hides its capabilities is therefore excluded by the architecture, and what appears in the list is the whole surface.

What the mod ecosystem looks like in practice is visible in the same video: one of the five mods is an account-tied connection to a commercial generation service, and a mod can add buttons that fire network calls on one keypress. The check for that is the same list, and one line in it can be the entire decision.

What we measured on our own machines

We looked read-only over SSH on 5 October 2026. In the code-server container on our appliance (VM 116, 192.168.180.206) Claude Code runs 2.1.283; on the host librechat-pve6 (192.168.180.2) it is 2.1.199. Both are below the 2.1.287 threshold, so mods are not available there yet. Neither machine has an /etc/claude-code/managed-settings.json, and the dsh box has no Claude Code binary at all. Our inventory dossier at docs/stack-inventory/inventory.md, last collected 7 September, does not mention Claude Code; the version layer for this tool was never collected.

So there is nothing to roll back today. That changes with the next update a developer takes on their own, because mods arrive enabled by default and we ship no managed settings. What we are missing is two undramatic things: an inventory sheet of agent CLI versions on the devices we can see, and an answer to who deploys managed settings. Running several of these CLIs on the same devices is the normal case by now, see the 2026 survey of the agent tools. Laptops we cannot reach from here are outside that measurement, which is the point rather than a caveat.

The model switchers, panels and games in the videos are consequences of the format. The part that concerns us is that a twenty-line JavaScript module can read files that were never in a user's context, and that this module is an officially supported way to build Claude Code. Allowing or banning mods is a decision you can only make once the versions are known and managed settings exist.

Further reading

Questions?
Do mods have to be enabled before they run?+

No. Mods are on by default from Claude Code 2.1.287. The environment variable from early access, CLAUDE_CODE_ENABLE_FUNCTION_HOOKS, is ignored from that version on, so setting it to 0 keeps nothing off. The organization-wide switch is allowManagedModsOnly on the built-in guard cc-plugin-sec-default@builtin, and that guard only loads where managed settings exist or where the user signed in with a Team or Enterprise plan.

Does denying Read of .env protect a developer's machine from a mod?+

Not from a mod. A denied deny rule and a managed PreToolUse hook take precedence over a mod's approval for Claude's own tool calls. They do not cover the mod's own $.fs and $.process calls, and the docs state the case directly: a mod can read a file that Read(.env) refuses with $.fs.read. We did not reproduce this on our own machines, because the installs we can see are below version 2.1.287.

Can a mod be reviewed before it is installed?+

Statically, yes. claude plugin validate <directory> runs nothing and still prints which events the module handles under hooks: and every mods API call it makes under calls:, including calls made from a registry function, with --strict and --json for a pipeline. That is an inventory, not proof, and it says nothing about what a listed $.process.run call then does on the machine. The reason the list is trustworthy is structural: the module has no Node access, and Claude Code refuses to load a module whose mods API usage the command cannot read.