You asked an agent to migrate a 200-file codebase from one HTTP client to another. It said yes, worked for a while, and returned a 4,000-line diff where some files were migrated, some were half migrated and two tests had quietly been edited to pass. The problem wasn't the model. It was the workflow: no plan you approved, no checkpoints, no gate.
This is a workflow for large refactors in Claude Code using plan mode. I checked the plan mode, permission and CLAUDE.md behaviour against Anthropic's docs on 2026-10-08 (pages: permission modes, common workflows and memory). I read the docs; I did not run a refactor for this post, so the workflow itself is a recommendation, not a measured result.
What does plan mode do, according to Anthropic?
From the permission-modes page: plan mode "tells Claude to research and propose changes without making them." Claude reads files, runs shell commands to explore and writes a plan, but doesn't edit your source. Edits stay blocked until you approve the plan.
Facts you need, as of 2026-10-08:
- Enter it: press
Shift+Tabuntil the status bar shows plan mode on, prefix one prompt with/plan, or start withclaude --permission-mode plan.Shift+Tabagain leaves it without approving. - Approve: Claude presents the plan and offers choices. As documented: "Yes, and use auto mode" (or "Yes, auto-accept edits" if auto mode isn't available to you), "Yes, manually approve edits", or "No, keep planning". Approving exits plan mode and switches to the mode you picked.
- Edit the plan:
Ctrl+Gopens the proposed plan in your text editor. - Default for a project: set
defaultModetoplanin.claude/settings.json. The VS Code extension doesn't read that for its starting mode; it usesclaudeCode.initialPermissionMode. - Caveat: in interactive terminal sessions with bypass permissions available, the docs say plan mode's blocks aren't enforced. Claude is instructed to plan, but an edit it attempts will run. Non-interactive
-pruns, Agent SDK sessions and the VS Code chat panel keep the blocks.
For a refactor, pick "manually approve edits" on the first pass until you trust the plan. Auto modes are fine for later, lower-risk steps once tests are your gate.
Step 1: write the refactor brief, then plan
Plan mode is only as good as what you ask. Put the goal, the boundaries and the definition of done in the first prompt. An untested template:
/plan Migrate src/ from requests to httpx.
Constraints: behaviour must not change; no edits to tests/ except
import lines; do not touch vendored/ or generated/.
Definition of done: pytest -q passes; grep -r "import requests" src/ is empty.
Produce: (1) an inventory of affected files grouped by module,
(2) an ordered list of steps, each small enough for one commit,
(3) for each step, the command that proves it worked,
(4) risks and anything you are unsure about.
The "tests may only change import lines" constraint matters. It is the cheapest defence against an agent making tests pass by editing them.
Step 2: review the plan like a pull request
Read it. Check for steps that are really three steps, missing files (run your own grep and compare), and any step with no verification command. Edit the plan with Ctrl+G or answer "No, keep planning" with specific changes. Ten minutes here is cheaper than reverting a branch.
A plan that says "update all call sites" is a wish. A plan that says "step 4: migrate src/billing/* (7 files), verify with pytest tests/billing -q" is a plan.
Step 3: work on a branch, one step per commit
Create a branch first. Then approve the plan and tell Claude to do one step at a time:
Do step 1 only. Run its verification command. Show me the diff stat
and the test output. Stop and wait. Do not start step 2.
Commit each step yourself, or have Claude commit after you've looked. Small commits give you git bisect and cheap reverts. If you want parallel work on independent steps, the common-workflows page documents claude --worktree <name> for isolated checkouts, which needs at least one existing commit.
Step 4: make tests the gate, not Claude's opinion
"It looks right" is not a gate. The gate is a command that exits non-zero when you're wrong: your test suite, type checker, linter and the grep from your definition of done. Before step 1, run them on the untouched branch and record the baseline. If tests are already red, fix or quarantine that first, or you won't know what you broke.
If the area has weak coverage, make the first plan step "add characterisation tests for current behaviour", and review those before any code moves. For hard guarantees such as "never edit generated/", the memory docs are explicit that CLAUDE.md is context, not enforced configuration, and point to a PreToolUse hook to block an action regardless of what Claude decides. See Claude Code hooks and the hooks lesson.
Step 5: keep CLAUDE.md short and put the refactor rules in it
Per the memory docs, CLAUDE.md and auto memory are loaded at the start of every conversation, and the recommendation is to keep each file under about 200 lines because longer files consume context and reduce adherence. The docs also say project-root CLAUDE.md is re-read from disk after /compact, while instructions given only in conversation can disappear. That matters on a long refactor: put durable rules in the file, not in chat.
A refactor section worth adding:
## Refactor rules
- One behaviour-preserving step per commit.
- Run `pytest -q` and `mypy src` before declaring a step done.
- Never edit files under generated/ or vendored/.
- If a test fails, report it. Do not change the test to pass.
For file structure and what else belongs there, see CLAUDE.md and .cursorrules guide and the lesson on project memory.
Step 6: review the whole diff before merge
Per-step review catches local errors. A final pass catches the global ones. Check:
git diff --stat main...against the plan: any file touched that the plan didn't list?- Test files:
git diff main... -- tests/should show only what you allowed. - Deleted code: was each removal intended?
- Run the full suite and the type checker one last time, from a clean checkout if you can.
Then ask Claude for a review in a fresh session with no memory of writing the code, and treat its findings as leads to verify. The permissions and settings lesson covers locking down what a session can run, and real-world workflows shows adjacent patterns.
When should I skip plan mode?
Skip it for a one-file rename or a typo. A plan for a two-minute change costs more than it saves. Use it when the change touches many files, when you don't know the codebase well, or when a wrong turn is expensive to undo. One more honest limit: a plan reflects what Claude found by reading code, and it can miss dynamic imports, reflection, config-driven wiring and anything outside the repo. Your tests and greps are the check on that.
Quick checklist
- Branch created, baseline tests green, results recorded.
- Brief states constraints, definition of done and verification per step.
- Plan reviewed and edited; no step without a verification command.
- One step per commit; stop after each.
- Hard rules in hooks or tests, soft rules in CLAUDE.md.
- Final diff reviewed against the plan, tests untouched except as allowed.



