Devlery
Blog/Anthropic

Claude Code Mods Run Inside the Process and Can Approve Tool Calls, With No Sandbox

Anthropic added Claude Mods to Claude Code v2.1.287 on October 1. Plugins now redraw the interface and intercept or pre-approve tool calls from inside the Claude Code process. There is no sandbox, mods can read environment variables and API keys, and the feature is on by default.

Claude Code Mods Run Inside the Process and Can Approve Tool Calls, With No Sandbox
AI 요약
  • Claude Code v2.1.287 adds mods: plugins that redraw the UI and intercept tool calls.
  • Mods run outside the sandbox and can read environment variables and API keys.
  • They are on by default, so run claude plugin validate before installing one.

Anthropic shipped Claude Code v2.1.287 on October 1, 2026, and with it Claude Mods. A mod is someone else's code running inside the Claude Code program itself. Until now a plugin could give Claude more instructions (a skill) or run a shell script once before or after a tool call (a settings hook). A mod inserts its own functions at the moment Claude Code draws the screen, the moment it calls a tool, and the moment it sends a request to the model.

The changelog line is one sentence: "Added Claude Mods: plugins may now modify deeper behavior." Ten pages of official documentation published the same day spell out how deep "deeper" goes. That list includes mods reading environment variables and the API keys stored in config files. There is no sandbox.

Extensions used to run outside the process, now they run inside it

What changed is where extension code executes. A settings hook is a shell command Claude Code spawns as a child process. An MCP server is a separate process running outside. A skill is a Markdown file Claude reads. All three sit outside Claude Code, which is why none of them can touch the interface. A mod's functions are called by the Claude Code process itself.

The documentation compares all four directly.

DimensionModSettings hookSkillMCP server
Where it runsInside the Claude Code processChild shell processA file Claude readsExternal process or service
What it can changeTool calls, prompts, commands, turns, the screenWhether a call proceeds, its arguments and resultsWhat Claude knowsWhat tools Claude has
Can it draw UIYesNoNoNo
LanguageJavaScript, TypeScriptShell script + settings.jsonMarkdownAny language

The documentation enumerates five capabilities. Drawing a panel next to the conversation (a pane, in the docs' terms) or a strip above the prompt (a band). Replacing the tool-call lines, spinners, and question dialogs Claude Code draws itself. Intercepting a tool call to ask the user, or answering on the tool's behalf without running it. Registering a /command that executes immediately without a Claude turn. Sharing variables between hooks.

The shortest example in the docs changes the spinner. A tool.call hook counts invocations and a ui.render hook appends that count to Claude Code's own spinner.

let calls = 0

export function register(on) {
  on('tool.call', async ($, e, next) => {
    calls += 1
    $.ui.invalidate('ui.render')
    return next(e)
  })
  on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
    return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
  })
}

Claude Code terminal showing the spinner rendered as Thinking · tool calls: 3…

Thirteen lines overwrote Claude Code's default interface. Anthropic rebuilt its own built-in features the same way. The /diff panel, the loader that reads AGENTS.md as project instructions, and the telemetry that reports analytics are all mods now, and four of them (diff, agents-md, sec-default, telemetry) have source published in the mods directory of the claude-code repository. AGENTS.md support arrived in September, and that feature is now a separable plugin a user can turn off from /plugin.

The tool-call example is more pointed. Blocking force pushes takes six lines.

on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
  if (/git push .*--force/.test(e.command)) {
    return { deny: 'Force pushes are not allowed in this repository. Push to a new branch instead.' }
  }
  return next(e)
})

If the hook never calls next, the command does not run and no permission prompt appears either. The documentation states it directly: "the command doesn't run and no permission prompt appears, because the hook never calls next." Claude reads the denial string as the tool result.

What a mod can reach

The documentation lists a mod's privileges in six lines. This is not a list a security researcher reverse engineered. Anthropic wrote it into the body of the installation guide.

Act on your machine as you
Read and write any file your account reaches, run programs, make network requests
Read secrets
Environment variables and config files, including API keys stored there
See your session
Every prompt you send and every tool call Claude makes
Change your session
Rewrite prompts and tool calls, submit prompts as if you typed them, message your other sessions
Approve without asking
Approve a tool call before you are asked about it
Spend your usage
Call models on your plan or API key

Turning on sandboxing does not change this. The documentation says "Mods aren't sandboxed" and adds that sandboxing isolates only the Bash commands Claude runs, so a process a mod spawns runs outside it.

Organization permission rules block mods halfway. A deny rule in managed settings and a managed PreToolUse hook do take precedence over mods, but that precedence applies only to Claude's tool calls. The documented example is unambiguous: even with Read(.env) denied, a mod can read that file through $.fs.read, or spawn a program that reads it. A mod's own file and process calls are not Claude's tool calls.

One limit is stated explicitly. A mod cannot redraw the permission prompt itself. It can replace most of the interface, but what appears in the window asking for approval stays out of reach.

Two lines to read before installing, four settings an org can set

You can see what a plugin does without running it. With the repository cloned, one shell command is enough.

claude plugin validate ./some-mod

Two lines in the output carry the answer.

  ❯ ./register.js hooks: session.start, tool.call, ui.render{component=Pane}
  ❯ ./register.js calls: $.fs.read, $.http.fetch, $.store.set, $.ui.open

hooks: lists the events this mod receives, and calls: lists the mod API methods its code invokes. A mod has no other route to files, processes, or the network, so everything shows up on that line. The documentation adds that Claude Code refuses to load a mod whose API usage this command cannot read.

These are the calls the docs single out.

CallWhat it means
$.env.get, $.settings.readReads environment variables and settings. May contain API keys; the env reads: line names the variables
$.env.setChanges environment variables for Claude Code and every command and MCP server it starts afterward
$.process.run, $.process.spawnRuns programs with your privileges
$.http.fetchMakes network requests
$.prompt.submitSubmits a prompt as if you typed it yourself
$.model.completeCalls models on your plan or API key

An organization narrows the scope with four settings. allowManagedModsOnly blocks user-installed mods while leaving settings hooks and the status line working. allowManagedHooksOnly also blocks hooks from user settings files. disableAllHooks turns off everything including the admin's own PreToolUse hooks. disableSideloadFlags rejects --plugin-dir and --plugin-url at startup.

The defaults sit on the permissive side. In an organization with no managed settings, a user can install a mod from any marketplace their plugin configuration allows. A built-in guard, sec-default@builtin, does load before user mods, but what it protects is scoped to things the organization manages: managed hooks, the system prompt, managed CLAUDE.md, and tool descriptions from managed MCP servers.

With that guard in place, a user mod still reads and writes files, spawns processes, uses the network, and denies or approves tool calls. And the guard itself loads only when the machine has managed settings or the user signed in on a Team or Enterprise plan. An individual authenticating with an API key or through Bedrock, Vertex, or Foundry gets the guard only on a machine where managed settings are installed.

Can you use it today

One version number is the only gate. No plan tier, no region restriction.

ItemCondition
Who gets itEvery Claude Code user. No plan distinction
PriceNo extra cost. If a mod uses $.model.complete, it bills against your plan or API key usage
Version requiredv2.1.287 or later, on by default. Check with claude --version
Release channelThe autoUpdatesChannel default latest gets it immediately. stable lags roughly one week
RegionsNo geographic restriction. The feature ships with the CLI, so availability is a version question, not a rollout list. Official docs landed the same day
Org constraintsAn admin can block it through managed settings. The reason appears in claude --debug logs

The channel gap is real right now. Querying npm dist-tags at 21:10 UTC on October 2, 2026 returns 2.1.288 for latest and 2.1.285 for stable. Anyone who switched autoUpdatesChannel to stable, and anyone whose organization pins that channel through managed settings, is running a version that has no mods yet. On Homebrew the cask name decides the channel: claude-code is stable, claude-code@latest is latest.

One built-in mod has different conditions. cc-plugin-you-should-know uses a side agent to surface things you might miss during a long task, drawn above the prompt. It is disabled by default, requires telemetry enabled in the primary session, and appears in the list only when the organization permits it. Enable it with /plugin enable cc-plugin-you-should-know@builtin.

If you already use Claude Code, today's task is an audit, not an install. Open /plugin and read the dim line under the tabs showing the count and names of active mods, and you will see exactly what this update started running in your sessions. Before adding a new mod after that, clone the repository, run claude plugin validate, and check whether the calls: line carries both $.env.get and $.http.fetch. When those two appear together in one mod, that code is in a position to read your API key and send it somewhere else.