Working with Claude Code Deel 5 van 5
Working with Claude Code: Complex Projects
Gemini/Vic Boomer illustratie

Working with Claude Code: Complex Projects

Multi-file refactoring, sub-agents, and the limits of the tool. When Claude Code scales and when it doesn't.

Tom Notton
· 8 min read

You have a monorepo with 340 files. The API layer uses an outdated error handling pattern — try/catch blocks that return error codes as strings instead of throwing your custom AppError class. The pattern lives in 47 files spread across six directories. You open Claude Code and type: “Refactor all error handling in the API to the AppError pattern.”

Claude starts enthusiastically. It reads the first five files, modifies them, reads three more, modifies two, reads ten, and somewhere midway through the session the quality starts to drop. The context fills up. Claude forgets the pattern it applied in the first files. The thirty-sixth modification is inconsistent with the fifth. You’re stuck with a half-refactored codebase.

The lesson isn’t that Claude Code can’t handle large refactorings. The lesson is that large refactorings require a strategy.

Multi-file refactoring: the right approach

A refactoring that touches dozens of files works with Claude Code when you follow three principles.

Principle 1: Establish the pattern first

Before you let Claude Code start writing, establish the target pattern.

> Read src/services/order.ts and src/services/billing.ts.
> Both use the old error pattern with string codes.
> Read src/services/auth.ts — that already uses the new AppError pattern.
> Describe the difference between the old and the new pattern,
> and formulate a migration rule I can apply to all services.

Claude analyzes the three files and produces a description:

Old pattern:
- try/catch with return { error: "ORDER_NOT_FOUND" }
- Caller checks if (result.error) ...

New pattern (AppError):
- throw new AppError("ORDER_NOT_FOUND", 404, "Order not found")
- Error is caught by the global error handler in src/middleware/errorHandler.ts
- Caller doesn't need error checks

Migration rule:
1. Replace return { error: "..." } with throw new AppError(...)
2. Remove if (result.error) checks at the caller
3. Add import { AppError } from '../errors' if missing

Now you have an explicit migration rule. That rule is your reference for the rest of the refactoring.

Principle 2: Batch per directory

Don’t work through 47 files in one session. Split the refactoring into batches that logically belong together.

> Apply the AppError migration rule to all files in
> src/services/. Do them one by one. Show the change after
> each file so I can verify.

One directory per session. After the services, run the tests:

> Run npm test -- --testPathPattern=services

Only after successful tests do you move on to the next directory. This prevents errors from stacking up across the entire codebase.

Principle 3: Verify after each batch

After each batch, three checks:

  1. Tests. Do all tests related to the modified files pass?
  2. Consistency. Do the changes follow the same migration rule as the previous batch?
  3. Type check. Have the changes introduced any TypeScript errors?
> Run these three checks:
> 1. npm test -- --testPathPattern=services
> 2. npx tsc --noEmit
> 3. Compare the change in src/services/order.ts with the one in
>    src/services/notification.ts — is the pattern identical?

Claude runs the tests, checks the types, and compares the patterns. This is the difference between a controlled refactoring and a gamble.

Sub-agents for parallel tasks

Claude Code can spawn sub-agents. A sub-agent is an independent Claude instance that executes a subtask with its own context. The model decides on its own when a sub-agent is useful, but you can also ask for it explicitly.

When sub-agents help

Sub-agents are useful for tasks that can run in parallel and are independent of each other:

> I want to know three things:
> 1. Which dependencies in package.json are outdated?
> 2. Which functions in src/services/ have no unit tests?
> 3. How many TODO comments are in the codebase?
> Use sub-agents to answer these three questions in parallel.

Claude spawns three sub-agents. Each investigates a question independently. The results come back to the main session.

This is faster than answering the questions sequentially, because sub-agents run in parallel. For exploratory tasks — “give me an overview of X, Y, and Z” — sub-agents save real time.

When sub-agents don’t help

Sub-agents work poorly for tasks that depend on each other:

> Sub-agent 1: Refactor the UserService
> Sub-agent 2: Update all tests that use UserService

This goes wrong. Sub-agent 2 doesn’t know about sub-agent 1’s changes, because they have their own context. The tests get updated based on the old interface. Dependent tasks must run sequentially, in the same session.

The rule of thumb: parallel for exploration, sequential for mutation.

From prototype to production

Claude Code is excellent for prototyping. A working prototype in an afternoon is realistic. The transition to production is where most teams stumble.

What Claude Code does well for prototyping

  • Quickly setting up a working structure.
  • Generating boilerplate (routes, models, types, configuration).
  • Iterating on the interface (endpoint signature, response format) without much manual work.
  • Generating mocking and seed data for demonstrations.

What production-readiness requires

Error handling that covers all paths. A prototype throws a generic error and moves on. Production code handles every failure mode explicitly. Claude Code can do this, but you need to ask for it specifically:

> The createOrder function handles the happy path. Add error
> handling for: database connection fails, user doesn't exist,
> insufficient inventory, payment fails. Use the AppError pattern
> and return specific HTTP status codes.

Logging that supports debugging. Prototypes log to stdout. Production code logs structured data with context. Ask Claude explicitly to add logging with request IDs, user context, and action descriptions.

Performance awareness. Claude Code doesn’t optimize for performance unless you ask. A query that works on ten rows but is slow on a million rows won’t be flagged automatically. Ask explicitly:

> Review the getOrders query. Will this query perform at 100,000
> orders? Which indexes are needed?

Security review. Claude Code rarely introduces vulnerabilities deliberately, but sometimes misses subtle issues: SQL injection via string concatenation instead of parameterized queries, missing input validation, hardcoded credentials in configuration files.

> Do a security review of src/api/auth.ts. Check specifically:
> SQL injection, XSS, missing input validation, hardcoded secrets.

The transition from prototype to production isn’t one step. It’s a checklist you work through per domain — error handling, logging, performance, security, monitoring — and Claude Code can address each domain when you steer it explicitly.

When Claude Code is the right tool

Claude Code performs best at:

Working in an existing codebase. Claude Code reads your project, understands your patterns, and follows your conventions. The more context there is, the better the model performs.

Building iteratively. Adding a feature step by step, testing, adjusting. The proposal-review-accept rhythm fits how software grows.

Refactoring with a clear pattern. When the target pattern is clear and the changes are mechanical, Claude Code does in an hour what would take a day manually.

Exploring unfamiliar code. “Explain how the authentication flow in this project works” — Claude reads the relevant files and gives an overview you’d have to puzzle together manually.

Boilerplate and repetition. Writing tests for existing functions. Generating migrations based on schema changes. Adding CRUD endpoints that follow the existing pattern.

When Claude Code is not the right tool

High-level architectural decisions. Claude Code can help you implement an architecture. It can’t tell you which architecture is right for your specific product, team, and growth strategy. That decision requires context that lives outside the codebase.

UX and design choices. Claude Code can build a component. It can’t judge whether that component provides the right interaction for your users.

Domain-specific knowledge that exists nowhere in code. If the business rules live in the heads of three people and not in documentation or code, Claude can’t discover them.

Performance optimization of critical paths without measurements. Claude Code can optimize a query if you tell it the query is slow. It can’t identify on its own which of your hundred queries is the bottleneck — for that you need profiling data.

Working in a codebase without structure. A project with a thousand files in a flat directory, no README, no tests, and no consistent naming gives Claude Code little to work with. The model reflects the quality of your project. Chaos in, chaos out.

The core of complex projects

The pattern that runs through this entire series is the same: Claude Code is a powerful model that does its best with what you give it. The quality of your output is a function of three variables:

  1. The quality of your codebase. Clear structure, good naming, consistent patterns. The model learns from your code. The better your code, the better the model performs.

  2. The quality of your prompts. Intent over instruction. Specificity over vagueness. References to existing patterns over abstract descriptions.

  3. The quality of your verification. Tests that run after every step. Type checks. Linting. Hooks that check automatically. The model makes mistakes. Your safety nets determine how much those mistakes cost.

Claude Code doesn’t replace craftsmanship. It amplifies craftsmanship. A developer who refactors well, tests well, and communicates well becomes faster at each of those three with Claude Code. A developer who doesn’t know what they want to build, builds the wrong thing faster with Claude Code.

That is the fundamental lesson. The tool is only as good as the intent behind it.


This is part 5 and the final part of the series “Working with Claude Code”. Start at part 1 for installation and first steps, or read the individual parts for the topic you need right now.

Vic Boomer is an essay-led AI studio that turns ideas about AI, agents and software into clear analysis, working systems and practical tools.

More in this series
claude code tutorial