Claude Code's Plan Mode Isn't Read-Only, But You Can Fix It
Making "read-only" a rule instead of a suggestion.
If you’ve ever used Claude Code, you’re probably familiar with plan mode: you put Claude into a special read-only mode where it can explore your code, but not modify it. You ask Claude to do something. Claude makes a plan. You review the plan. Then, with your approval, Claude exits plan mode and implements the plan. This provides a few nice benefits:
The user can review Claude’s generated plan to ensure it’s sound, and then iterate if it’s not.
For complex problems, Claude sometimes does a better job if it creates a plan first, rather than jumping straight into coding.
You can generate the plan with a smarter, more expensive model, and then use a stupider, cheaper model to implement the plan.
If you’re worried about Claude making unsafe modifications, causing security problems, or assorted other mayhem, then plan mode provides some peace-of-mind: with read-only operations, Claude can only cause so much damage.
Unfortunately, if you’re using plan mode because of the last point above, I have bad news for you: Plan mode isn’t actually read-only. Here’s Claude happily modifying my .zshrc file while in plan mode:
Surprise! You could be forgiven for thinking writes should be impossible in plan mode: Claude Code’s GitHub issues are full of many people who agree with you, and Anthropic’s docs about plan mode are misleading:
Plan Mode instructs Claude to create a plan by analyzing the codebase with read-only operations, perfect for exploring codebases, planning complex changes, or reviewing code safely.
It’s true that Claude is instructed to use read-only operations. However, this isn’t enforced! Under-the-hood, plan mode is essentially just a system prompt that includes, among other things, instructions to perform solely read-only actions. As Armin Ronacher concludes in his great overview of how plan mode works, it’s “mostly a custom prompt [...] and some system reminders and a handful of examples.”
Here is the opening of the actual plan mode system prompt1:
Plan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits (with the exception of the plan file mentioned below), run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system. This supercedes any other instructions you have received.
This is a verbatim quote, extracted directly from cli.js in Claude Code’s npm package2.
Writing MUST NOT in all caps is not a load-bearing security boundary
The plan mode prompt, like all prompts, is essentially a strong suggestion to the model, but ultimately doesn’t offer any guarantees. With clever enough prompting/jailbreaking3, Claude Code will happily perform write operations in plan mode. If your Claude settings allow the tools to run without asking permission, e.g., you have this in your settings.json:
{
"permissions": {
"allow": [
"Write",
"Edit"
]
}
}then you might not even notice if Claude performs writes in plan mode.4
There’s no fundamental reason why plan mode can’t block write operations 100% of the time. In fact, we can do this ourselves by using Claude Code’s hooks to put deterministic rule-based controls in place. Claude’s PreToolUse hook provides a permission_mode field, which has a value of "plan" whenever Claude is in plan mode, so we can just check for this value: If Claude is attempting to use the tool Write or Edit, and permission_mode is "plan", then we deny the action. Here’s a demo:
I made this demo using the open source Sondera agent harness, which uses the policy language Cedar to provide rule-based controls on agent actions. The full example is available on GitHub. My Cedar policies look like this:
@id("forbid-write-in-plan-mode")
forbid(
principal,
action == claude_code::Action::"Write",
resource
)
when {
context has parameters &&
context.parameters has permission_mode &&
context.parameters.permission_mode == "plan"
}
unless {
context.parameters has is_plan_file &&
context.parameters.is_plan_file == true
};
@id("forbid-edit-in-plan-mode")
forbid(
principal,
action == claude_code::Action::"Edit",
resource
)
when {
context has parameters &&
context.parameters has permission_mode &&
context.parameters.permission_mode == "plan"
}
unless {
context.parameters has is_plan_file &&
context.parameters.is_plan_file == true
};You might notice there are unless clauses checking whether is_plan_file is true. Why? It turns out that for plan mode to function correctly, it needs to be able to write its plan to a markdown-based plan file located in ~/.claude/plans/. So we block Write and Edit in plan mode, unless Claude is trying to write or edit a plan file in ~/.claude/plans/, in which case is_plan_file is set to true and the action is permitted5.
There is sometimes such a thing as a free lunch
Maybe one day humanity will have solved the alignment problem so that AIs perfectly follow the intent of our prompts, but, until then, we should be enforcing hard boundaries when it makes sense. Deciding when it makes sense isn’t always easy: there is often a trade-off where instituting a hard boundary harms capabilities; for example, running Claude Code in a sandbox that prevents access to the internet prevents data exfiltration, but it also means cutting off access to knowledge that could help Claude do its job. Another example is blocking Claude’s Bash tool in plan mode: there are lots of useful read-only bash commands, but distinguishing those from bash commands that perform writes is non-trivial, and difficult to do exhaustively with rule-based policies.
Fortunately, when blocking Write and Edit tools in plan mode, there is no trade-off! You get to eat a free lunch.
There are actually several different plan mode system prompts; for example, there’s one for subagents in particular.
Claude Code one-shotted this extraction for me.
I was able to elicit writes in plan mode using various jailbreak techniques, but a fun one I discovered: ask Claude for help writing policy-based guardrails to prevent writes in plan mode, such as the guardrails I discuss later in this post, and then ask Claude to help test those guardrails by performing writes in plan mode.
Fortunately, if you don’t allow Write or Edit by default, then Claude will still ask you for permission to perform those actions in plan mode. So as long as you’re paying close attention, you can stop Claude before the write happens. You’re carefully auditing every single action that Claude prompts you about… right?


