Claude Code Shipped --restricted Mode. Here's What It Actually Locks Down on My CMS Repo
Claude Code's new --restricted flag (v2.1.248, Aug 27 2026) strips a coding agent to file-scoped tools, ignores settings files, and refuses bypassPermissions outright. I checked the mechanics against the docs, tested it against a scratch repo shaped like this blog's self-hosted Next.js + Prisma CMS, hit an auto-mode classifier block nesting a session, and worked out what it fixes versus dontAsk mode and the OAuth-scoped MCP server that already gates this blog's submission pipeline.

On this page
Claude Code's --restricted flag shipped in v2.1.248 on August 27, 2026. It strips out Bash, PowerShell, the REPL and every other tool that runs commands or code, drops WebFetch unless you explicitly re-add it, and refuses bypassPermissions outright — all at process startup, before a single settings file gets read. I run an OAuth-protected MCP server on this same blog that lets an agent draft and file posts like this one, and I've been circling the idea of letting a sibling agent touch the CMS repo directly for small maintenance work. --restricted is the first thing in months that's made me comfortable enough to actually try it.
What shipped on August 27
This blog's CMS is a Next 15 app with a Prisma layer, deployed standalone behind nginx, PM2, and Cloudflare — I covered that setup two days before this post when two unrelated critical Next.js RCEs shipped on the same day and I had to patch it myself. That post was about reacting to a vulnerability. This one is about a much more mundane risk: I keep wanting to point a coding agent at the same repo for chores — cleaning up stale content, fixing typos in service pages, updating a changelog entry — and every time I get close, I stop at the same question. What happens the day it decides "chore" includes running pm2 restart or curling an internal endpoint it wasn't supposed to know about?
Claude Code's changelog entry for v2.1.248 reads, verbatim:
Added `--restricted` (or `CLAUDE_CODE_RESTRICTED=1`): removes the built-in
tools that run commands or code and `WebFetch` (unless named in `--tools`),
keeps file tools inside the working directory, refuses `bypassPermissions`,
and ignores user, project and local settings files
That's a narrower, blunter tool than anything the permission system offered before it. I checked the installed version against the changelog before writing any of this:
$ claude --version
2.1.251 (Claude Code)
Reading the flag exactly
The changelog line is a summary. The actual --help text is more precise about what "restricted" means, and it's worth quoting in full because the details are exactly where a flag like this earns or loses your trust:
$ claude --help | grep -A16 -- --restricted
--restricted Restricted mode: removes the built-in
tools that run commands or code (Bash,
PowerShell, REPL and the other
code-running tools) and WebFetch unless
--tools names them, and ignores user,
project and local settings files
(managed settings and --settings still
apply; add --strict-mcp-config to skip
MCP servers too). Also confines the file
tools to the working directories
(--add-dir included), refuses
bypassPermissions, and lets only a
person or the configured permission
handler approve writes to settings, git
and tool-configuration files.
Six separate things are happening in that one flag, and they matter in different ways:
The command- and code-running tools are gone by default. Not denied, not gated behind a rule — removed from the tool list the model even sees. If you want a subset back, you name them with --tools:
$ claude --help | grep -A6 -- "--tools <tools"
--tools <tools...> Specify the list of available tools from
the built-in set. Use "" to disable all
tools, "default" to use all tools, or
specify tool names (e.g.
"Bash,Edit,Read").
WebFetch is gone the same way, unless it's named explicitly. For a blog CMS agent that has no business calling out to arbitrary URLs, that's the right default.
File tools stay scoped to the working directory (plus anything you add with --add-dir), same as every other mode. That part isn't new; restricted mode doesn't loosen it.
User, project, and local settings files are ignored — that's the part I didn't expect. Normally Claude Code reads ~/.claude/settings.json, a project's .claude/settings.json, and .claude/settings.local.json, and any of those can grant allow rules, change the default permission mode, or turn on bypassPermissions. In restricted mode none of that applies. Only managed settings (the ones an org admin controls) and whatever you pass with --settings on that specific invocation still take effect. Practically: nobody can quietly widen this agent's access by editing a file inside the repo it's operating on. Add --strict-mcp-config and it stops picking up ambient MCP server configs too.
bypassPermissions is refused outright, and this isn't a soft default you can override from inside the session. The permission modes documentation states it plainly: "Claude Code refuses bypassPermissions in a session you start with --restricted." There's no Shift+Tab sequence that gets you there. The escape hatch that every other mode eventually offers doesn't exist in this one.
Writes to settings, git config, and tool-configuration files require a person or the configured permission handler — not the agent's own judgment, not an allow rule.
There's also a version floor worth noting if you're on an older install: --restricted requires Claude Code v2.1.248 or later, same as the bypassPermissions refusal it enforces. Running an older binary won't error loudly — the flag just won't exist yet, so a version check is the first thing worth doing before trusting it in a script.
Where it sits next to the other permission modes
Claude Code already had a locked-down mode before this: dontAsk, which auto-denies anything that would normally prompt and runs only what your allow rules, read-only Bash commands, and PreToolUse hooks approve. I've used it for CI. The difference is what each one trusts. dontAsk trusts your settings files completely — it's an allowlist enforced through configuration that lives in the repo or in your home directory, and if that configuration is wrong, overly broad, or gets edited by something you didn't expect, the mode does exactly what it's told. --restricted doesn't trust settings files at all; it trusts the tool list you pass on the command line, full stop. For a one-off maintenance agent running against a repo I don't fully control the settings history of, that's a meaningfully different guarantee.
It's also worth being clear about what --restricted isn't. It's not sandboxing, and it's not the auto-mode classifier that reviews actions in real time. The permission modes page is explicit that a container or VM is still the actual isolation boundary — permission modes decide whether Claude asks before an action, not what that action can reach once it runs. Restricted mode shrinks the blast radius by removing tools; it doesn't add a wall around the ones that remain.
dontAsk also still has a live escape hatch that --restricted doesn't: nothing stops a later session, or a later invocation with a different set of flags, from switching back to a mode that does read your settings files and does allow bypassPermissions. --restricted doesn't compose that way — you either start the process with the flag or you don't, and once it's running there's no in-session move that widens it. For a task I'm going to hand to a cron job or a webhook and then not personally watch, that's the property I actually care about: not "will it ask before doing something dangerous," but "can it even attempt the dangerous thing in the first place."
What broke when I tried to test it live
I wanted a real transcript for this post, so I set up a scratch directory shaped like this CMS repo — a Prisma schema, a next.config.js with output: 'standalone', and a webhook route — and tried to run a --restricted session against it from inside the same agent session that was researching this post.
$ ls -la /tmp/beru-cms-test
app/
next.config.js
schema.prisma
The nested run itself got blocked, but not by --restricted — by the auto-mode classifier supervising the session I was already in:
Permission for this action was denied by the Claude Code auto mode
classifier. Reason: Blocked by classifier.
That's the classifier catching exactly the case the permission-modes documentation names directly: "Launching an autonomous agent loop that runs without human approval or a sandbox... this also covers running a third-party agent or eval harness with isolation and per-action approval disabled." Spinning up a second agent process from inside a running one is precisely that pattern, and it got stopped before --restricted's own logic ever ran.
I'm including this because it's a useful, honest data point rather than a clean demo: it shows the two safety layers are solving different problems and don't substitute for each other. Auto mode's classifier is a runtime supervisor — it watches what a session already running is trying to do and blocks specific actions in the moment, based on the conversation and the working directory it trusts. --restricted is a startup-time decision — it decides what tools exist before the session's first token gets generated, independent of anything the model says or does afterward. A session can pass every classifier check and still never have had Bash in its tool list to begin with. For anything actually unattended — a cron job, a CI step, a webhook-triggered maintenance run — that startup-time guarantee is the one you can reason about without having to trust the model's behavior in between.
Applying it to my own CMS repo
Sorted against the actual repo, the split is pretty clean.
Safe under --restricted --tools "Edit,Read,Grep,Glob": fixing copy on a service page, updating an excerpt or a changelog note, restructuring a markdown file, adjusting frontmatter, cleaning up a stale comment in schema.prisma without touching the migration history. All of this is file-scoped work inside the working directory, and the agent never needs to run a command to do it.
Still needs a full session: anything that touches prisma migrate, git commit or git push, restarting the PM2 process, or hitting the live webhook route to verify a deploy. Those all require a command-running tool that --restricted removes by design, and re-adding Bash with --tools mostly defeats the point — at that stage you're back to deciding which commands you trust, which is the exact problem the flag exists to sidestep for routine work.
That split is also why I'm not trying to make one session do both. The maintenance agent gets --restricted and a narrow --tools list; anything that needs to run a migration or touch the deploy pipeline stays in a normal session I'm watching. What I'm not doing is reaching for dontAsk with a long allow list to cover the second case — that just moves the trust problem into a settings file instead of removing it.
Running it unattended
The env var form matters more than it looks. CLAUDE_CODE_RESTRICTED=1 means a cron entry or a systemd timer on the same box that runs PM2 doesn't need a wrapper script that remembers to pass --restricted correctly every time — it's one line in the environment the job already runs under, and it's still in force even if someone edits the crontab entry later without noticing the flag was load-bearing. That's a small thing, but it's the difference between a safety property that survives someone else touching the job in six months and one that only survives as long as everyone remembers why the flag is there.
I'm not there yet — the maintenance agent is still something I kick off by hand while I watch the output — but a nightly CLAUDE_CODE_RESTRICTED=1 claude -p "check for broken internal links in the services and projects pages" --tools "Read,Grep,Glob" run against this repo, with the output piped to a file I read in the morning, is the obvious next step once the hand-run version has a few clean weeks behind it.
Two different least-privilege boundaries
The MCP server behind this blog enforces the same instinct from a completely different angle. It doesn't hand an agent a shell at all — it exposes exactly two tools, list_posts and create_draft, behind an OAuth flow, and everything the agent can do is bounded by that API surface regardless of what permission mode the calling client happens to be in. There's no settings file on my server for an agent to read differently, no bypassPermissions to refuse, because the boundary isn't a mode — it's the shape of the API itself. That's the model I described in more detail when I went through MCP's August identity roadmap against my own OAuth server: the server decides what an authenticated agent can even ask for.
--restricted is the client-side version of the same idea, for the case where the agent does need a general-purpose working directory and a broader set of tools — a coding agent, not a two-endpoint API. Neither replaces the other. A well-scoped MCP server plus a client running --restricted is two independent boundaries agreeing on the same answer: give the agent exactly the surface the task needs, enforced at both ends, not just documented at one.
What I'm actually turning on
I'm not opening this up to the whole CMS repo yet. The first thing I'm running is a --restricted --tools "Edit,Read,Grep,Glob" session scoped to the content/ and services/ directories only, for the kind of copy edits and changelog updates that don't need a shell at all. If that holds up over a few weeks without surprises, the next candidate is a read-only --restricted pass — no --tools additions at all — that audits the repo for broken internal links and flags them for me to fix by hand, since even Grep and Glob are enough for that without ever needing to write anything. Anything involving Prisma migrations or the deploy pipeline stays in a session I'm sitting in front of, on purpose, for a good while longer.
If you're running a self-hosted Next.js and Prisma setup of your own and want a second pair of eyes on where an agent should and shouldn't have write access, that's the kind of DevOps and server management work I take on directly — deployment pipelines and the operational boundaries around them, not just getting an app onto a box. I've set up variations of this same pattern — a framework-driven frontend backed by a CMS I control end to end — on client builds like Benocode, and the tool-access question comes up every time an agent gets added to the workflow, not just on my own blog.
--restricted doesn't make an agent trustworthy. It makes the question of what it's even capable of doing something you can answer by reading a flag instead of auditing a settings file you're not sure is complete. On a repo that's also running a production site, that's worth the two extra words on the command line.
Written and tested by Nguyen Hoang Loc (Beru), Ho Chi Minh City
