%%{init: {'theme': 'neutral', 'flowchart': {'useMaxWidth': false, 'htmlLabels': true, 'padding': 20, 'nodeSpacing': 30, 'rankSpacing': 40}, 'themeVariables': {'primaryColor': '#8B9DAF', 'primaryTextColor': '#ffffff', 'primaryBorderColor': '#6E7F91', 'secondaryColor': '#9CAF88', 'secondaryTextColor': '#ffffff', 'secondaryBorderColor': '#7A8D68', 'tertiaryColor': '#C2856E', 'tertiaryTextColor': '#ffffff', 'tertiaryBorderColor': '#A06A54', 'lineColor': '#B5A99A', 'textColor': '#4A4A4A', 'mainBkg': '#8B9DAF', 'nodeBorder': '#6E7F91', 'clusterBkg': 'rgba(139,157,175,0.12)', 'clusterBorder': '#B5A99A', 'edgeLabelBackground': 'transparent'}}}%%
flowchart TD
P1["Phase 1: Explore<br><i>Read codebase, understand patterns</i>"]
P2["Phase 2: Write Plan<br><i>Create .claude/plan.md</i>"]
P3["Phase 3: Verify<br><i>Self-check completeness</i>"]
P4["Phase 4: Present<br><i>Show plan to user</i>"]
GATE{"User decision"}
EXEC["Exit to execution<br><i>ExitPlanMode tool</i>"]
CANCEL["Cancel"]
P1 --> P2 --> P3 --> P4 --> GATE
GATE -- "approve" --> EXEC
GATE -- "modify" --> P2
GATE -- "reject" --> CANCEL
style P1 fill:#8B9DAF,color:#fff,stroke:#6E7F91
style P2 fill:#9CAF88,color:#fff,stroke:#7A8D68
style P3 fill:#C2856E,color:#fff,stroke:#A06A54
style P4 fill:#B39EB5,color:#fff,stroke:#8E7A93
style GATE fill:#C4A882,color:#fff,stroke:#A08562
style EXEC fill:#8E9B7A,color:#fff,stroke:#6E7B5A
style CANCEL fill:#C2856E,color:#fff,stroke:#A06A54
Plan Mode
Read-only planning with explicit approval gates
Why Separate Planning from Execution?
An AI agent that can read, write, and execute shell commands is powerful but dangerous. A user asking “refactor the auth module” does not necessarily want the agent to immediately start editing files. They might want to see the plan first: which files will change, in what order, with what dependencies. Plan mode enforces this separation — the agent can explore and reason but cannot modify anything until the user explicitly approves.
This is the same principle as git diff --staged before git commit, or a database migration’s --dry-run flag. The cost of planning is low (a few read-only API calls). The cost of a bad execution is high (corrupted files, broken builds, wasted tokens on wrong approaches). Plan mode makes the asymmetry explicit.
This post covers:
- The 5-phase planning workflow and its variants
- Read-only enforcement via permission lockdown
- The plan agent — a specialized read-only sub-agent
- Plan file persistence and user review
- The approval gate and transition to execution
- Integration with the agent loop, mode cycling, and system reminders
Source files covered in this post:
| File | Purpose | Size |
|---|---|---|
src/commands/plan/plan.tsx |
/plan command handler |
~200 LOC |
src/utils/planModeV2.ts |
Plan mode V2 agent configuration | ~150 LOC |
src/tools/AgentTool/built-in/planAgent.ts |
Plan agent definition (read-only constraints) | ~100 LOC |
src/utils/plans.ts |
Plan file utilities (read/write .claude/plan.md) |
~80 LOC |
src/tools/EnterPlanModeTool.ts |
Tool to enter plan mode | ~50 LOC |
src/tools/ExitPlanModeTool.ts |
Tool to exit plan mode with pre-approved permissions | ~80 LOC |
The 5-Phase Planning Workflow
Plan mode follows a structured 5-phase workflow that guides the agent from codebase exploration to a reviewable plan document. Each phase has a clear objective and a defined transition condition.
How to read this diagram. Follow the phases top to bottom: the agent explores (Phase 1), writes a plan (Phase 2), verifies it (Phase 3), and presents it (Phase 4). The diamond is the user’s decision gate — approve exits to execution, modify loops back to Phase 2 for revision, and reject cancels entirely. The key insight is that the loop-back from “modify” to Phase 2 means the user can iterate on the plan without ever leaving read-only mode.
Phase 1: Explore. The agent reads the codebase to understand existing patterns, dependencies, and constraints. It uses only read-only tools: Read, Glob, Grep, and read-only Bash commands (ls, git status, git log, git diff, find, cat). This phase builds the agent’s mental model before it commits to any strategy.
Phase 2: Write Plan. The agent writes a structured implementation plan to .claude/plan.md in the project directory. The plan includes: which files to modify, in what order, with what changes, and what dependencies exist between steps. This is the only “write” operation in plan mode — and it writes to a plan file, not to source code.
Phase 3: Verify. The agent self-checks the plan for completeness. Does it cover all files mentioned in the user’s request? Are there circular dependencies between steps? Is the ordering feasible? This phase catches planning errors before the user sees the plan.
Phase 4: Present. The agent presents the plan to the user for review. The user can approve, modify, or reject.
Three plan mode variants exist, selected by the system based on context:
| Variant | When used | Phases | Traffic share |
|---|---|---|---|
| 5-phase | Default for interactive sessions | All 5 phases | ~99% |
| Iterative | Smaller tasks, simpler plans | Lighter 3-phase | ~1% |
| Subagent | Sub-agents spawned in plan mode | Adapted for non-interactive | Rare |
Read-Only Enforcement
Plan mode is not advisory — it is enforced at the permission layer. When plan mode is active, the permission system blocks all non-read-only tools. The agent physically cannot write files, run destructive commands, or spawn sub-agents that can write.
The enforcement works through the same canUseTool() injection mechanism described in Part II.1: Agent Loop. Plan mode injects a policy function that checks each tool’s isReadOnly flag:
// Plan mode permission policy (simplified)
canUseTool: (tool) => {
if (tool.isReadOnly) return 'allowed';
return 'denied: plan mode is read-only';
}In addition to the permission function, a system reminder is injected into every API call while plan mode is active:
Plan mode is active. The user indicated that they do not want you
to execute yet -- you MUST NOT make any edits... or otherwise make
any changes to the system.
This dual enforcement — policy function and system reminder — provides defense in depth. The policy function is the hard gate (the model cannot bypass it). The system reminder is the soft guide (it shapes the model’s reasoning to avoid even attempting blocked actions, saving wasted tool calls).
%%{init: {'theme': 'neutral', 'flowchart': {'useMaxWidth': false, 'htmlLabels': true, 'padding': 20, 'nodeSpacing': 30, 'rankSpacing': 40}, 'themeVariables': {'primaryColor': '#8B9DAF', 'primaryTextColor': '#ffffff', 'primaryBorderColor': '#6E7F91', 'secondaryColor': '#9CAF88', 'secondaryTextColor': '#ffffff', 'secondaryBorderColor': '#7A8D68', 'tertiaryColor': '#C2856E', 'tertiaryTextColor': '#ffffff', 'tertiaryBorderColor': '#A06A54', 'lineColor': '#B5A99A', 'textColor': '#4A4A4A', 'mainBkg': '#8B9DAF', 'nodeBorder': '#6E7F91', 'clusterBkg': 'rgba(139,157,175,0.12)', 'clusterBorder': '#B5A99A', 'edgeLabelBackground': 'transparent'}}}%%
flowchart TD
MODEL["Model generates<br>tool call"]
L1{"Layer 1:<br>System reminder<br><i>soft guide</i>"}
L2{"Layer 2:<br>canUseTool()<br><i>hard gate</i>"}
READ["Execute<br><i>read-only tool</i>"]
BLOCK["Blocked<br><i>denied: read-only mode</i>"]
SKIP["Model avoids<br>write attempt"]
MODEL --> L1
L1 -- "model self-corrects" --> SKIP
L1 -- "model still tries" --> L2
L2 -- "isReadOnly: true" --> READ
L2 -- "isReadOnly: false" --> BLOCK
style MODEL fill:#8B9DAF,color:#fff,stroke:#6E7F91
style L1 fill:#9CAF88,color:#fff,stroke:#7A8D68
style L2 fill:#C2856E,color:#fff,stroke:#A06A54
style READ fill:#B39EB5,color:#fff,stroke:#8E7A93
style BLOCK fill:#C4A882,color:#fff,stroke:#A08562
style SKIP fill:#8E9B7A,color:#fff,stroke:#6E7B5A
How to read this diagram. Start at the top where the model generates a tool call. Layer 1 (system reminder) is a soft filter — if the model has internalized the read-only constraint, it self-corrects and never attempts the write (left branch). If the model still attempts a write, Layer 2 (canUseTool policy) is the hard gate — it checks the isReadOnly flag and either allows execution (read-only tool) or blocks with an error (write tool). The dual-layer design means that even if the model ignores the reminder, the policy catches it.
The Plan Agent
The plan agent is a specialized sub-agent type with a restricted tool set and an explicit read-only system prompt. It is defined in src/tools/AgentTool/built-in/planAgent.ts and can be spawned via subagent_type: "Plan".
| Property | Plan Agent | Default Agent |
|---|---|---|
| Tools | Read, Glob, Grep, Bash (read-only), WebFetch | All ~40 tools |
| Blocked tools | Agent, Edit, Write, NotebookEdit, ExitPlanMode | None by default |
| Permission mode | plan (read-only enforced) |
default or inherited |
| System prompt | Includes CRITICAL: READ-ONLY MODE header |
Standard agent prompt |
| Bash restrictions | Only: ls, git status/log/diff, find, grep, cat, head, tail |
Full shell access |
| Output format | Ends with “Critical Files for Implementation” section | Free-form |
The plan agent’s Bash restrictions deserve attention. Even though Bash is available, only a curated set of read-only commands is permitted. Commands like rm, mv, cp, mkdir, git add, git commit, npm install, and pip install are explicitly blocked in the system prompt. This is a belt-and-suspenders approach: the canUseTool policy blocks non-read-only tools, the Bash-specific restrictions block destructive commands within the one tool that has inherent write capability.
The plan agent’s system prompt opens with an unmistakable header:
“=== CRITICAL: READ-ONLY MODE — NO FILE MODIFICATIONS === This is a READ-ONLY planning task. You are STRICTLY PROHIBITED from: Creating new files (no Write, touch, or file creation of any kind). Modifying existing files (no Edit operations). Deleting files (no rm or deletion). Moving or copying files (no mv or cp). Creating temporary files anywhere, including /tmp. Using redirect operators (>, >>, |) or heredocs to write to files. Running ANY commands that change system state.”
The prompt then prescribes a four-step process: (1) Understand Requirements, (2) Explore Codebase, (3) Design Approach, (4) Detail Plan. The required output format ends with a “Critical Files for Implementation” section listing 3-5 files — this structured output makes the plan directly actionable when the user exits plan mode.
Plan File Persistence
The plan is persisted as .claude/plan.md in the project directory, managed by utilities in src/utils/plans.ts. This file is:
- Human-readable — Markdown format, viewable in any editor
- Editable — The user can modify the plan via
/plan open, which opens it in their configured editor (VS Code, Vim, etc.) - Persistent across sessions — The plan survives session restarts, enabling multi-session planning
- Referenced in system reminders — The
plan-file-referencesystem reminder tells the agent where to find the current plan
The /plan slash command (implemented in src/commands/plan/plan.tsx) serves three roles depending on arguments:
/plan → Toggle plan mode on/off; show current plan if active
/plan open → Open .claude/plan.md in external editor
/plan <description> → Enable plan mode and start agent with the given task
The Approval Gate: Exiting Plan Mode
The transition from planning to execution is mediated by the ExitPlanMode tool, a deferred tool (loaded on demand via ToolSearch) defined in src/tools/ExitPlanModeTool.ts.
%%{init: {'theme': 'neutral', 'flowchart': {'useMaxWidth': false, 'htmlLabels': true, 'padding': 20, 'nodeSpacing': 30, 'rankSpacing': 40}, 'themeVariables': {'primaryColor': '#8B9DAF', 'primaryTextColor': '#ffffff', 'primaryBorderColor': '#6E7F91', 'secondaryColor': '#9CAF88', 'secondaryTextColor': '#ffffff', 'secondaryBorderColor': '#7A8D68', 'tertiaryColor': '#C2856E', 'tertiaryTextColor': '#ffffff', 'tertiaryBorderColor': '#A06A54', 'lineColor': '#B5A99A', 'textColor': '#4A4A4A', 'mainBkg': '#8B9DAF', 'nodeBorder': '#6E7F91', 'clusterBkg': 'rgba(139,157,175,0.12)', 'clusterBorder': '#B5A99A', 'edgeLabelBackground': 'transparent'}}}%%
flowchart TD
PLAN["Plan mode active<br><i>permissionMode: plan</i>"]
USER["User approves plan"]
EXIT["ExitPlanMode tool"]
PRE["Pre-approved prompts<br><i>allowedPrompts[]</i>"]
EDITED["planWasEdited flag"]
MODE["Permission mode →<br><i>default</i>"]
EXEC["Agent executes plan<br><i>full tool access</i>"]
PLAN --> USER --> EXIT
EXIT --> PRE
EXIT --> EDITED
PRE --> MODE
EDITED --> MODE
MODE --> EXEC
style PLAN fill:#8B9DAF,color:#fff,stroke:#6E7F91
style USER fill:#9CAF88,color:#fff,stroke:#7A8D68
style EXIT fill:#C2856E,color:#fff,stroke:#A06A54
style PRE fill:#B39EB5,color:#fff,stroke:#8E7A93
style EDITED fill:#C4A882,color:#fff,stroke:#A08562
style MODE fill:#8E9B7A,color:#fff,stroke:#6E7B5A
style EXEC fill:#8B9DAF,color:#fff,stroke:#6E7F91
How to read this diagram. Start at the top in plan mode (read-only). When the user approves, the ExitPlanMode tool fires and carries two pieces of data: a list of pre-approved Bash prompts (commands the plan already specified, like “run tests”) and a flag indicating whether the user edited the plan. Both feed into the permission mode transition from plan to default, and the agent begins execution with full tool access. The pre-approved prompts bypass the normal permission gate, reducing friction for predictable commands.
The ExitPlanMode tool’s input schema has two notable fields:
interface ExitPlanModeInput {
allowedPrompts?: { tool: "Bash"; prompt: string }[];
planWasEdited?: boolean;
}allowedPrompts is the bridge between planning and execution. During planning, the agent identifies which commands will be needed (e.g., npm test, npm run build). These are passed to ExitPlanMode as pre-approved prompts. During execution, these specific commands bypass the normal permission gate — the user already approved them as part of the plan. This reduces the friction of repeated permission prompts for predictable commands.
planWasEdited tracks whether the user modified the plan file. If the user edited .claude/plan.md directly, the agent knows to re-read it before executing, rather than relying on its cached understanding of the plan.
The ExitPlanMode tool description carries two notable directives:
“IMPORTANT: Only use for implementation planning, not research tasks.”
“CRITICAL: Do NOT use
AskUserQuestionto ask ‘Is this plan okay?’ — that is what this tool inherently does.”
These directives prevent the model from misusing the approval gate: using it for research (which doesn’t need a plan file) or redundantly asking for permission before invoking the permission tool itself.
Integration with Mode Cycling and System Reminders
Plan mode integrates with the broader mode cycling system described in Part V.1: CLI, Commands & UI. The UI reflects plan mode with a cyan/teal color theme (vs. blue for standard mode, magenta for auto mode). Switching modes is an atomic operation that reconfigures three things simultaneously: the available tool set, the system prompt fragments, and the permission level.
Plan mode system reminders are among the most token-heavy reminders in the system (see Part III.1: Prompt Assembly). The 11 plan mode prompt fragments total approximately 13 KB and cover:
| Fragment | Purpose | Variant |
|---|---|---|
plan-mode-is-active-5-phase |
Full 5-phase planning instructions | Default |
plan-mode-is-active-iterative |
Lighter iterative planning | Small tasks |
plan-mode-is-active-subagent |
Subagent planning instructions | Sub-agents |
plan-mode-re-entry |
Instructions when re-entering after modification | Loop-back |
exited-plan-mode |
Transition reminder after exit | Exit |
verify-plan-reminder |
Prompt to self-check plan completeness | Phase 3 |
plan-file-reference |
Path to .claude/plan.md |
All phases |
phase-four-of-plan-mode |
Phase 4 presentation instructions | Phase 4 |
plan-mode-enhanced |
Enhanced plan agent role definition | Agent |
In full mode, the plan mode reminder consumes approximately 500 tokens. After compaction, it collapses to approximately 20 tokens (“Plan mode active. Continue current phase.”) — a 96% reduction. This is the same sparse/full pattern used by other system reminders (see Part III.2: Context Compaction).
Model selection optimization. When plan mode is active and the context exceeds 200K tokens, the system auto-demotes from Opus to Sonnet. Planning is primarily reading and reasoning — tasks where the smaller model performs nearly as well — so the cost saving is significant without meaningful quality loss.
Summary
Plan mode separates design from execution through a 5-phase workflow with an explicit user approval gate. Key mechanisms:
- Read-only enforcement via dual layers: system reminder (soft guide) +
canUseTool()policy (hard gate), implemented in the same permission injection system as other modes - Plan agent (
planAgent.ts) — a specialized sub-agent restricted to read-only tools with explicit Bash command whitelisting - Plan persistence —
.claude/plan.mdis human-readable, editable via/plan open, and survives session restarts - Approval gate —
ExitPlanModetool transitions permission mode fromplantodefault, carrying pre-approved commands that bypass the permission gate during execution - Three variants — 5-phase (default), iterative (light), subagent (non-interactive) — selected automatically based on context
- 11 prompt fragments (~13 KB) manage the agent’s behavior across phases, with sparse/full variants for token efficiency after compaction
Next: Part III.1 — Prompt Assembly examines how the system prompt is constructed from 250+ fragments — including the plan mode fragments described here.
Series: Inside Claude Code | Part II.4