How Do You Refactor Code With Claude Code?
Build the fence before you move anything. Commit your current work so the tree is clean, get one test passing that exercises the code you are about to change, and write a single sentence saying what behaviour must be identical afterwards. Then open the Claude Code desktop app, describe the one structural change you want, and refuse everything else. Run the test after each step. Commit after each step. That loop is the whole method, and it is boring on purpose.
Refactoring is the task where agent speed hurts you most. Writing new code has an obvious pass or fail at the end: the feature works or it does not. A refactor has no such signal. The app still runs, the code reads better, the diff looks professional, and the one edge case that used to be handled by a strange-looking conditional is now gone. You find out three weeks later. Everything below exists to make that outcome visible while it is still cheap.
The Fence-First Refactor
The Fence-First Refactor is the pattern we teach for this, and the name is the instruction. You do not move furniture before you know where the walls are. The fence is three things, all built before you write the first prompt, and each one takes under five minutes.
- A clean git state. Everything committed, nothing staged, a named branch for the refactor. This is what makes an unlimited number of retries free, and it is the single highest-value habit in agent-assisted work.
- A green test that touches the code. It does not have to be elegant. It has to fail if the behaviour changes. One test that calls the function with a realistic input and asserts the exact output beats a suite that only checks the file imports.
- A one-sentence contract. Write it in the prompt: the public function signatures stay identical and the API response for an existing order stays byte for byte the same. If you cannot write that sentence, you do not yet know what you are protecting.
The contract sentence is the piece people skip and the piece that does the most work. It converts a vague instruction into a checkable one, and it gives you something concrete to hold the finished diff against. It also gives Claude Code a boundary it can reason about instead of guessing at your intent from the shape of the code.
Refactoring Is Not Rewriting, and Claude Code Will Blur the Line
A refactor changes structure and leaves behaviour untouched. A rewrite changes both. Claude Code is extremely good at rewriting and it does not always announce the switch, because from inside the task both look like improving the file. You ask it to extract a function and it also renames three variables, converts a loop to a map, adds a null check that was not there, and removes a branch it judged unreachable. Four of those five are fine. The unreachable branch was reachable on Sundays.
- Named scope in the prompt. Say what may change and add the words: change nothing else, even if you see something worth improving.
- Ask for the list first. Have it read the file and list the changes it would make, ranked, before it edits anything. You pick one. This is what [plan mode](/blog/claude-code-plan-mode) is for and it costs you one extra exchange.
- Separate commits for separate intents. Structural moves in one commit, behaviour fixes in another, formatting in a third. A mixed commit is unreviewable and it hides the one line that mattered.
- Treat every removal as suspicious. Additions are easy to evaluate. Deletions are where the behaviour lives, and a deleted guard clause looks exactly like a deleted redundancy in a diff.
There is a version of this that is not the agent's fault. If your prompt says clean up this file, you have asked for a rewrite and you will get one. The word refactor carries a precise meaning in your head and a fuzzy one in a sentence, so replace it with the actual operation: extract, inline, rename, move, split, or deduplicate. One verb, one target, one commit.
Split the Refactor Into Seams
A seam is a place where the code can be cut without touching anything on the other side. Refactoring seam by seam is what turns an unreviewable 900-line diff into six diffs you can actually read. Find the seams before you start and the whole job becomes a sequence of small, verifiable moves rather than one long act of faith.
- Extract the pure logic first. Anything that takes inputs and returns outputs with no side effects moves cleanly and is trivial to test once it is out.
- Move the input and output edges next. Parsing, validation, and serialisation are usually tangled into the middle and they are the second easiest thing to pull free.
- Leave the stateful core for last. Anything that writes to a database, mutates shared state, or fires an external call is where the real risk is, and by the time you reach it the surrounding code is simple enough to read.
- Stop at the first surprise. If a move requires a change you did not predict, that is information about the system, not an obstacle. Commit what works, then investigate.
One seam per session is a good default for anything unfamiliar. Long sessions accumulate context that stops being relevant, and a refactor conversation that started with the parser is a poor place to discuss the database layer. Clear the session between seams and let the commit history carry the memory instead, which is the habit behind [Claude Code context management](/blog/claude-code-context-management).
The Prompt That Keeps a Refactor Honest
A good refactor prompt names the operation, the target, the contract, and the verification. Four parts, one message, no ambiguity about what counts as done. Here is the shape, and it works whether the target is a 40-line function or a service class nobody has opened in a year.
- The operation. Extract the date parsing out of processOrder into its own function. One verb, not clean up.
- The target file and boundary. In src/orders/process.ts only. Do not touch any other file.
- The contract. processOrder keeps its exact signature and return shape. No behaviour changes of any kind, including error messages.
- The verification. Run npm test after the change and show me the result. If anything fails, stop and tell me rather than fixing it.
That last line matters more than it looks. Without it, a failing test becomes a second, unrequested change made to satisfy the first one, and now you are reviewing a fix to a problem you never saw. Telling it to stop on red turns the test from a hurdle into a tripwire.
How to Refactor When There Are No Tests
You write the test first, and you write it to describe what the code currently does rather than what it should do. That distinction is the whole trick. You are not documenting correct behaviour, you are pinning down existing behaviour, including the bits that look wrong. If the function returns null on an empty array, your test asserts null. Fix it later, in a commit with its own name.
- Ask Claude Code to read the target file and describe every input it handles and every output it produces, including error paths. Read that list yourself and correct it.
- Have it write tests that assert the current output for each of those cases. Do not let it improve anything while writing them.
- Run the tests. Any that fail are telling you the description was wrong, which is worth knowing before you refactor and not after.
- Commit the tests on their own. Now the fence exists and the refactor can start.
This is also the fastest way to understand code you did not write, because a test that captures real behaviour teaches you more than a summary does. If the file is one of many and the whole codebase is unfamiliar, the wider approach in [Claude Code on a large existing codebase](/blog/claude-code-large-existing-codebase) is the place to start before you pick a file to improve.
Reviewing a Refactor Diff
Read the diff for what disappeared, not for what appeared. New code announces itself and is easy to judge. Removed lines are quiet, and a refactor is mostly removal. Every deleted conditional, early return, try block and default value is a question you have to answer before you merge.
- Scan deletions first. In a review, read every removed line before you read a single added one.
- Check the error paths. Structure changes routinely flatten specific error handling into a generic catch, and nothing fails until production.
- Confirm the signatures. If the contract said the public interface stays identical, verify that literally rather than trusting the summary.
- Watch for silent defaults. A value that used to be required and is now optional with a fallback is a behaviour change wearing a tidiness costume.
- Run the app, not just the tests. Tests confirm what you thought to check. Two minutes of clicking finds what you did not.
If the diff is too big to review properly, that is not a reason to skim it. It is a signal that the seam was too wide. Reset to the last commit and cut the job smaller. This is the general discipline in [review Claude Code output before you ship](/blog/review-claude-code-output-before-you-ship), and refactors are the case where it pays the most, because there is no feature to demo that would have caught the mistake.
A Refactor Routine You Can Run Every Time
Here is the whole Fence-First Refactor as a routine. It looks slower than just asking for a cleanup and it is faster in practice, because the time refactoring costs is almost never the editing. It is the week you spend finding out why an old customer's invoices stopped generating.
- Commit everything and create a branch named for the refactor.
- Get one test green that fails if the behaviour changes. Write it first if it does not exist, in its own commit.
- Write the contract sentence: what must be identical afterwards.
- Pick one seam. Name the operation, the file, the contract and the verification in a single prompt.
- Let it run, then read the deletions in the diff before anything else.
- Run the tests and open the app. If it is red, stop rather than layering a fix on top.
- Commit that seam alone with a message naming the operation.
- Clear the session and repeat for the next seam.
The branch and the per-seam commits are doing quiet work throughout: they make abandoning a bad attempt completely painless, which is what lets you be aggressive about resetting instead of trying to argue a confused session back on track. The [git workflow for Claude Code](/blog/claude-code-git-workflow) covers that safety net in full, and it is the prerequisite for everything here.
Short, practical drops on skills, MCP, agents, prompts, and more. No spam, unsubscribe anytime.
Frequently asked questions
Can Claude Code refactor code safely?
Yes, if you constrain it. The risk is not that it writes bad code, it is that a refactor quietly becomes a rewrite and changes behaviour you did not ask it to touch. Commit everything first, get one test green that fails when behaviour changes, name the exact operation rather than saying clean this up, and review the deleted lines in the diff before the added ones.
How do I stop Claude Code from changing things I did not ask for?
Name the boundary explicitly and add the phrase: change nothing else, even if you see something worth improving. Restrict it to one named file. Ask it to list the changes it would make and let you pick one before it edits anything. Standing rules like never mix formatting with logic belong in your project's CLAUDE.md so they apply in every session without retyping.
How big should a refactor be in one go?
Small enough that you can read the whole diff in one sitting without losing attention. In practice that is one seam: one extraction, one rename, one move. If the diff has grown past what you will genuinely review, the answer is to reset to the last commit and cut the job smaller, not to skim it and merge.
What if the code has no tests to refactor against?
Write the tests first, and write them to describe what the code currently does rather than what it should do. If a function returns null on an empty array, assert null even if that looks wrong. Commit those tests on their own, then start refactoring. Fixing the behaviour you disagree with is a separate task with its own commit.
What is the difference between refactoring and rewriting?
A refactor changes structure and leaves behaviour identical. A rewrite changes both. The distinction matters because they need different levels of verification, and the word refactor is fuzzy enough in a prompt that you will often get the second when you asked for the first. Use the precise verb instead: extract, inline, rename, move, split or deduplicate.
Should I refactor with Claude Code before or after adding a feature?
Before, in a separate commit, whenever the existing structure is what makes the feature awkward. Mixing a refactor into a feature commit produces a diff nobody can review, because there is no way to tell which changes were required and which were tidying. Refactor to make the change easy, commit, then make the easy change.
Last reviewed by David Iya on August 22, 2026


