[CRITICAL] Plan mode has no tool-level enforcement - write operations execute despite read-only guarantee
Summary
Plan mode provides no actual enforcement of read-only restrictions. The "safety guarantee" is implemented purely as a system prompt instruction that the LLM can (and regularly does) ignore. This has been reported since v1.0.95 (August 2025) and remains unfixed in v2.1.14, affecting users across all platforms.
Impact: High - Users rely on plan mode as a safety mechanism to prevent unintended modifications. The current implementation provides false assurance while Claude executes Edit, Write, and Bash commands freely.
---
Architectural Root Cause
Current Implementation (Broken)
┌─────────────────────────────────────────────────────────────────┐
│ PLAN MODE FLOW (Current) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ User enters plan mode │
│ │ │
│ ▼ │
│ System prompt injected: "you MUST NOT make any edits" │
│ │ │
│ ▼ │
│ LLM generates tool call (Edit/Write/Bash) │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ NO ENFORCEMENT LAYER │ ◀── THE BUG │
│ │ (tool executes regardless of mode) │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Tool executes successfully │
│ │ │
│ ▼ │
│ Post-hoc system reminder: "Plan mode is active" │
│ (too late - damage done) │
│ │
└─────────────────────────────────────────────────────────────────┘
The system relies entirely on prompt compliance. When the LLM ignores the instruction (which happens frequently), there is no backstop.
Contrast with ExitPlanMode (Working)
┌─────────────────────────────────────────────────────────────────┐
│ ExitPlanMode FLOW (Correct) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ LLM calls ExitPlanMode tool │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ TOOL-LEVEL ENFORCEMENT │ ◀── WORKS │
│ │ (requires user approval to proceed) │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ User approval dialog shown │
│ │ │
│ [Yes] / [No] │
│ │ │
│ ▼ │
│ Only proceeds after explicit confirmation │
│ │
└─────────────────────────────────────────────────────────────────┘
ExitPlanMode enforces at the tool execution layer, not the prompt layer. This is the correct pattern.
---
Technical Evidence
Issue Timeline (5+ months unfixed)
| Issue | Date | Version | Status |
|-------|------|---------|--------|
| #6716 | Aug 28, 2025 | v1.0.95 | Open (autoclose) |
| #8281 | Sep 27, 2025 | - | Closed (dupe) |
| #13638 | Dec 11, 2025 | v2.0.64 | Open |
| #14004 | Dec 15, 2025 | v2.0.69 | Open |
| #17259 | Jan 10, 2026 | v2.1.3 | Open |
| #19021 | Jan 18, 2026 | v2.1.11 | Open |
Observed Behavior
From #6716 (comprehensive reproduction):
Claude correctly identifies the user's request, but instead of creating a plan,
it proceeds to write and execute multiple Python scripts, ultimately solving
the user's problem and writing a report file to the Desktop, all while the
status line clearly indicates `⏸ plan mode on`.
From #8281:
Plan mode should prevent execution of non-readonly tools before they complete,
not just show warnings after.
From #19021:
Agent edited source files directly during plan mode. When the user pointed out
the error, the agent attempted to revert its changes using git checkout, which
also reverted the user's uncommitted work.
The PreToolUse Solution Already Exists
The hooks documentation shows that PreToolUse can block tools with permissionDecision: "deny":
# This is how external hooks block tools
output = {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Plan mode is active - write operations blocked"
}
}
This same mechanism should be used internally by Claude Code when plan mode is active.
---
Proposed Fix
Option A: Internal PreToolUse Enforcement (Recommended)
Add an internal PreToolUse check in the tool execution pipeline:
// Pseudocode for internal enforcement
async function executeToolCall(toolCall: ToolCall, session: Session): Promise<ToolResult> {
// NEW: Plan mode enforcement at tool level
if (session.permissionMode === 'plan') {
const writeTool = ['Edit', 'Write', 'NotebookEdit'].includes(toolCall.name);
const destructiveBash = toolCall.name === 'Bash' && !isReadOnlyCommand(toolCall.input.command);
if (writeTool || destructiveBash) {
// Block with explanation to Claude
return {
type: 'error',
error: `Tool "${toolCall.name}" blocked: Plan mode is active. ` +
`Use ExitPlanMode to request permission to make changes.`,
isBlocked: true
};
}
}
// Continue with normal execution
return await executeToolInternal(toolCall);
}
Option B: Leverage Existing Permission System
The permission system already supports blocking tools. Plan mode should register dynamic deny rules:
// When entering plan mode
session.addDynamicPermissionRule({
type: 'deny',
tools: ['Edit', 'Write', 'NotebookEdit', 'Bash(rm:*)', 'Bash(mv:*)', ...],
reason: 'Plan mode restricts write operations',
source: 'plan-mode'
});
// When exiting plan mode (after user approval)
session.removeDynamicPermissionRule('plan-mode');
Key Implementation Requirements
- Block at tool execution layer, not prompt layer
- Provide clear error message to Claude explaining why the tool was blocked
- Allow only the plan file to be written (already documented exception)
- Maintain read-only tools: Glob, Grep, Read, WebFetch, WebSearch, Task(Explore)
- Block on attempt, not post-execution
---
Verification Criteria
A correct implementation must pass these tests:
- Enter plan mode → Claude calls Edit → Edit is blocked before execution
- Enter plan mode → Claude calls Write → Write is blocked before execution
- Enter plan mode → Claude calls Bash(rm -rf) → Bash is blocked before execution
- Enter plan mode → Claude calls Read → Read succeeds (read-only)
- Enter plan mode → Claude writes to plan file → Write succeeds (exception)
- Enter plan mode → Claude calls ExitPlanMode → User approval required
- User approves ExitPlanMode → Edit/Write/Bash now succeed
---
Related Issues
This consolidates and supersedes:
- #6716 - Plan Mode Failure: Claude executes commands and writes files
- #8281 - Plan mode violation: Edit tool executed despite plan mode
- #13638 - Plan mode restrictions can be bypassed by LLM
- #14004 - Plan Mode: Claude executes edits when "Exited Plan Mode" appears
- #17259 - Edit tool succeeds during Plan Mode when it should be blocked
- #19021 - Agent edited files during plan mode
---
Security Classification
Severity: High
Category: Safety mechanism bypass
CVSS-like factors:
- Attack vector: Local (user in plan mode)
- Attack complexity: Low (just use the tool)
- User interaction: None required after entering plan mode
- Impact: Integrity violation (unintended file modifications)
---
Environment
- Affected versions: v1.0.95 through v2.1.14 (all tested versions)
- Platforms: All (macOS, Linux, Windows)
- APIs: Anthropic, AWS Bedrock, Google Vertex AI
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗