Claude Code subagents are the fastest way to parallelize a large refactoring or feature build, but only if you carve the work along lines that don’t require live negotiation between agents. This guide gives an ordered path for spawning claude code subagents from a shell or Python harness, merging their output, and steering clear of the context-isolation traps that burn tokens without shipping code.
When to reach for subagents
Don’t spawn claude code subagents for a task that fits in one coherent context window. The moment you split, you pay a coordination tax: each agent re-derives assumptions, and none see the others’ intermediate state. Use subagents when the work is structurally independent—different modules, different files, different concerns with a stable interface between them.
Good candidates:
- Extracting a service from a monolith (API surface vs. data layer vs. CLI).
- Writing unit tests for an existing module while another agent adds a feature elsewhere.
- Porting a frontend component library to a new framework while another handles the build config.
Bad candidates:
- A single function that needs to be rewritten with awareness of its 10 call sites.
- Anything where the “interface” is still being discovered.
If you can’t write a one-paragraph contract that each agent can follow without seeing the others’ code, keep it single-agent.
Defining subagent scopes
Before launching anything, write a scope file for each agent. This is plain markdown that you’ll feed as the prompt. Be explicit about what the agent must not touch.
Example agent-api.md:
You are refactoring the REST API layer in ./src/api.
- Only modify files under ./src/api and ./openapi.yaml.
- Do NOT edit ./src/db or ./src/cli.
- Target: split the auth middleware into a standalone module.
- Output a git commit when done. Use `git checkout -b api-refactor`.
Do the same for agent-db.md and agent-cli.md. The hard boundaries prevent overlapping edits that cause merge hell later.
Contract-first splitting
If the agents must interact, define the contract before spawning. For a TypeScript project, that might be a shared types file committed to a contracts/ branch:
// contracts/types.ts
export interface User {
id: string;
email: string;
role: "admin" | "user";
}
Each agent pulls this file at startup. Without it, you’ll get three slightly different User shapes.
Spawning claude code subagents from a script
Claude Code’s print mode (-p) runs a prompt non-interactively and exits. Combine that with git worktrees to isolate each agent’s filesystem.
# create worktrees
git worktree add ../wt-api -b api-refactor
git worktree add ../wt-db -b db-refactor
git worktree add ../wt-cli -b cli-refactor
# launch agents in parallel
cd ../wt-api && claude -p "$(cat ../agent-api.md)" --model claude-3-5-sonnet &
cd ../wt-db && claude -p "$(cat ../agent-db.md)" --model claude-3-5-sonnet &
cd ../wt-cli && claude -p "$(cat ../agent-cli.md)" --model claude-3-5-sonnet &
wait
The & backgrounds each process; wait blocks until all finish. Each agent operates in its own worktree, so they can’t stomp on each other’s files.
Python orchestrator for richer control
If you need timeouts, logging, or conditional retries, use Python’s subprocess:
import subprocess, asyncio
async def run_agent(path, prompt_file, model):
cmd = ["claude", "-p", open(prompt_file).read(), "--model", model]
proc = await asyncio.create_subprocess_exec(
*cmd, cwd=path,
stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
)
out, err = await asyncio.wait_for(proc.communicate(), timeout=600)
if proc.returncode != 0:
raise RuntimeError(f"agent failed: {err.decode()}")
return out.decode()
async def main():
tasks = [
run_agent("../wt-api", "agent-api.md", "claude-3-5-sonnet"),
run_agent("../wt-db", "agent-db.md", "claude-3-5-sonnet"),
run_agent("../wt-cli", "agent-cli.md", "claude-3-5-sonnet"),
]
await asyncio.gather(*tasks)
asyncio.run(main())
This gives you a single place to add fallback logic if an agent crashes.
Merging and verifying
Once the agents exit, you have three branches. Don’t blindly git merge. Run the test suite in each worktree first:
for wt in wt-api wt-db wt-cli; do
(cd $wt && npm test)
done
If tests pass individually, merge into a staging branch in order of dependency—contract first, then consumers:
git checkout -b staging
git merge db-refactor
git merge api-refactor
git merge cli-refactor
npm test
Conflicts at this stage are usually interface drift. If you defined the contract up front, they’re rare.
Common pitfalls
Context thrash. Engineers spawn 10 subagents for a task that needs 2. Each agent loads the repo, reads files, and emits a commit. The token cost scales linearly, and the merge overhead scales worse. Start with the minimum split.
Shared file edits. Even with scope files, an agent may decide to “fix” a shared config because its tests failed. Use .gitignore or pre-commit hooks to block edits outside the allowed tree. A simple guard:
# in each worktree, after agent runs
git diff --name-only | grep -vE '^(src/api|openapi.yaml)' && echo "SCOPE VIOLATION" && exit 1
Silent contract drift. Agent A changes the return type of getUser() but only documents it in its own worktree. Agent B’s tests pass locally, then break on merge. Enforce a single source of truth in contracts/ and fail CI if that file changes without a corresponding bump in a version string.
Over-promising the merge. Subagents don’t know the whole system. A merge may compile but violate an invariant none of them saw. Always run integration tests on the staging branch, not just unit tests per worktree.
Tradeoffs vs. a single agent
A single Claude Code session keeps the entire task in one context. It reasons about cross-cutting changes holistically. The cost is latency and context saturation on big repos.
Claude code subagents trade that holistic view for wall-clock speed. On a 50-file refactor with clean boundaries, three agents finish in roughly the time one would take to process a third of the files. On a tightly coupled change, they’ll produce three plausible but incompatible halves.
Rule of thumb: if the diff touches fewer than 15 files or spans a single subsystem, stay single-agent. Above that, with clear seams, split.
A complete ordered path
- Identify seams. List modules with stable interfaces.
- Write contract. Commit shared types/API spec to a
contractsbranch. - Author scope files. One per agent, with explicit “do not touch” paths.
- Create worktrees. Isolate filesystem state.
- Spawn in parallel. Use bash
&or Pythonasynciowith timeouts. - Test per worktree. Reject agents that break their own scope.
- Merge in dependency order. Contracts first.
- Run integration suite. Catch drift the unit tests missed.
Following this, claude code subagents become a force multiplier instead of a merge-nightmare generator. The discipline is in the scoping, not the spawning—most failures come from skipping step 2.