Use Claude Code for large-repository refactoring by selecting a narrow boundary, establishing current behavior, making small edits and reviewing independent test and runtime evidence. Repository size is a reason to control scope, not a reason to authorize a whole-codebase rewrite without a clear acceptance contract.
Key takeaways
- Map the relevant module and its callers before editing.
- Separate behavior-preserving refactoring from intentional feature changes.
- Record baseline tests and important runtime signals such as query counts.
- Review each focused diff before expanding scope.
- Use version control and a recovery plan; session rewind is not a universal rollback.
Scope the refactor around one behavior
For a fictional reporting module, the task might be to extract database loading from response mapping while preserving ordering, fallback values and query behavior. That is reviewable. “Modernize the reporting system” mixes architecture, behavior and delivery decisions.
Inventory the entry point, callers, contracts and data access. Identify generated files and unrelated modules that should remain untouched. Record public behavior and important internal constraints such as transaction boundaries or one-query loading.
| Area | Record before editing | Stop condition |
|---|---|---|
| Contract | Inputs, outputs, errors and ordering | Proposed incompatible behavior change |
| Dependencies | Callers, modules and database boundary | Unapproved cross-module access |
| Tests | Known passing/failing baseline | Cannot explain a new failure |
| Runtime | Relevant query or resource behavior | Regression outside agreed tolerance |
| Recovery | Clean commit and external side-effect inventory | Uncontrolled production or data change |
Do not combine an extraction with dependency upgrades, renamed APIs and new caching unless those are separately approved changes. Smaller changes make it easier to identify which decision caused a regression.
A Claude Code refactoring workflow
Claude Code’s common-workflow guidance describes exploring code, planning and refactoring in small, testable increments. Apply that pattern to a bounded module and provide repository-specific build and test instructions.
The permission documentation describes the execution and configuration controls. Review the effective mode and organization settings before a refactor. Keep production credentials and unnecessary network access outside the task environment.
- Establish a clean baseline: identify existing user changes and record a known repository commit.
- Explore read-only first: ask for the module boundary, callers and relevant tests.
- Approve the behavior contract: specify what must remain unchanged and which files are in scope.
- Request one extraction: avoid unrelated architecture changes.
- Run focused checks: compare outputs and relevant runtime evidence with the baseline.
- Review the diff: inspect code and test changes together.
- Broaden carefully: run wider checks when dependencies or uncertainty justify them.
A proposed prompt for the reporting example is:
Explore the reporting loader and its callers without editing first.
Propose a focused extraction of query execution from response mapping.
Preserve public output, ordering, missing-author fallback and SQL count.
Do not upgrade dependencies or change unrelated files.
After the plan is reviewed, implement the extraction and run the
baseline output fixtures and query-count checks.
Report the diff, checks actually run and remaining limitations.This is a prompt recipe, not a transcript from a Claude Code execution. We did not run Claude Code against a large repository for this guide. The local example below demonstrates the verification pattern independently.
A runnable behavior-preserving extraction
We created a small Python/SQLite fixture with books and authors. The original function performs a parameterized join and maps rows inline. The extracted version moves loading into a helper and leaves mapping in the caller. Save this complete example as refactoring_query_lab.py and run it with Python:
"""Tiny behavior-preserving extraction, not a large-repository or Claude benchmark."""
import sqlite3
def before(db,ids):
if not ids:return []
sql='SELECT b.id,b.title,a.name FROM books b LEFT JOIN authors a ON a.id=b.author_id WHERE b.id IN ('+','.join('?' for _ in ids)+') ORDER BY b.id'
return [{'id':i,'title':title,'author':name or 'Unknown'} for i,title,name in db.execute(sql,ids)]
def load_rows(db,ids):
if not ids:return []
sql='SELECT b.id,b.title,a.name FROM books b LEFT JOIN authors a ON a.id=b.author_id WHERE b.id IN ('+','.join('?' for _ in ids)+') ORDER BY b.id'
return db.execute(sql,ids).fetchall()
def after(db,ids):
return [{'id':row[0],'title':row[1],'author':row[2] or 'Unknown'} for row in load_rows(db,ids)]
def profile(db,fn,ids):
queries=[];db.set_trace_callback(lambda s:queries.append(s) if s.lstrip().upper().startswith('SELECT') else None)
result=fn(db,ids);db.set_trace_callback(None)
return result,len(queries)
def run():
db=sqlite3.connect(':memory:')
db.executescript("CREATE TABLE authors(id INTEGER,name TEXT); CREATE TABLE books(id INTEGER,title TEXT,author_id INTEGER); INSERT INTO authors VALUES(1,'Author A'); INSERT INTO books VALUES(1,'One',1),(2,'Two',99),(3,'Three',1);")
cases=[([1,2],[{'id':1,'title':'One','author':'Author A'},{'id':2,'title':'Two','author':'Unknown'}]),([],[]),([3],[{'id':3,'title':'Three','author':'Author A'}]),([99],[])]
for ids,expected in cases:
old,old_count=profile(db,before,ids);new,new_count=profile(db,after,ids)
assert old==new==expected
assert old_count==new_count==(1 if ids else 0)
print('8 assertions passed: 4 output fixtures and unchanged SELECT counts (1 per nonempty request, 0 empty)')
if __name__=='__main__':run()
The placeholder list is built from the number of supplied identifiers; identifier values are passed as query parameters. The helper preserves the same join and ordering. This example is deliberately tiny and is not a recommended architecture for every application.
Keep the same fixtures for both implementations: two books with one missing author, an empty request, a single existing book and a nonexistent book. Expected results are defined separately from the new implementation.
For a real repository, use representative domain cases and the actual persistence technology. Missing rows, null behavior and ordering may be part of a public contract even when they are not obvious from a method signature.
Query profiling and test evidence
The local lab passed eight assertions: four output comparisons and four query-count comparisons. SQLite tracing observed one SELECT for each nonempty request and zero for the empty request in both versions. No latency improvement or large-repository benchmark is claimed.
| Fixture | Output result | Before / after SELECT count |
|---|---|---|
| Existing books with missing author | Expected rows and Unknown fallback preserved | 1 / 1 |
| Empty request | Empty output preserved | 0 / 0 |
| Single existing book | Expected record preserved | 1 / 1 |
| Nonexistent identifier | Empty output preserved | 1 / 1 |
Why measure queries during a refactor? A new abstraction can accidentally move loading into a loop. Output tests may remain green while the request now performs many queries. Preserve the important nonfunctional boundary as well as the response shape.
The GraphQL N+1 guide explains a related query-count problem. Here the goal is preservation, not a performance optimization.
Query count alone is not complete performance evidence. For a real service, assess representative volume, query plans, memory, transactions and latency where relevant. Do not extrapolate a tiny SQLite fixture to production throughput.
What can and cannot be rolled back?
Claude Code’s checkpointing guidance describes tracking and rewinding edits, with limits on what is captured. Treat it as a session aid. Keep version control for reviewed source history and explicit recovery for database, external-system and deployment changes.
Before editing, identify existing uncommitted work. Preserve it and isolate the refactor so a recovery operation does not erase somebody else’s changes. Review file-level changes instead of using a broad destructive reset as a routine fix.
A file revert does not undo a database migration, email, published package or remote API action. A behavior-preserving refactor should avoid those side effects during local verification. If a change requires them, create a separate controlled plan.
When a regression appears, stop expanding the diff. Compare against the baseline, identify the smallest failing change and correct or revert that reviewed change through the normal repository process.
Before accepting the refactor
- The public behavior and scope are unchanged unless explicitly approved.
- Existing user changes are preserved.
- Tests still assert the original contract.
- Relevant query, transaction and resource behavior is checked.
- No unnecessary dependency, permission or deployment change is included.
- Reported test results match evidence.
- Source recovery and external side-effect boundaries are understood.
- A human owner can explain the new code.
Use the spec-driven development guide for the behavior contract and the generated-code checklist for security review.
Common questions
How can Claude Code refactor a large repository safely?
Scope one module or behavior, map callers, establish tests, use controlled execution, make small changes and review independent evidence before expanding.
Should refactoring change behavior?
Keep behavior-preserving refactoring separate from intentional feature or contract changes so each can be reviewed and tested clearly.
Why profile queries during a refactor?
A new abstraction can accidentally increase database calls without changing outputs. Query checks help detect that regression.
Does session rewind replace version control?
No. Keep reviewed source history and a separate recovery plan for data and external effects.
Was the example a large-repository Claude benchmark?
No. It was a small local Python/SQLite extraction with eight passing assertions and unchanged query counts.
Refactor through small changes and clear evidence
Bring the module, baseline tests and intended boundary. Discuss AI-assisted engineering adoption with a focused refactoring and verification plan.
