Automate Multi-File Refactors with Claude Code Subagents A developer guide published by Mariana Souza on Sourcefeed demonstrates how to build a two-subagent refactor pipeline in Claude Code 2.1.263, using a read-only scout and a surgeon subagent behind a /refactor command that cannot modify the test suite. The tutorial includes code for a demo repo with Node.js 20.19.6 and requires a Claude Pro, Max, Team, Enterprise, or Console account. Automate Multi-File Refactors with Claude Code Subagents Build a scout-and-surgeon subagent pipeline behind a /refactor command that cannot touch your test suite. Mariana Souza https://sourcefeed.dev/u/mariana souza What you'll build A two-subagent refactor pipeline in Claude Code https://code.claude.com/docs/en/overview : a read-only scout that inventories every call site, a surgeon that edits one file at a time, and a /refactor slash command that drives both behind a permission boundary that can't touch your tests. Prerequisites Verified on Claude Code 2.1.263 macOS/Linux/WSL and Node.js https://nodejs.org 20.19.6 . Node 22+ if you install Claude Code through npm. claude --version 2.1.263 Claude Code node --version v20.19.6 or later You need a Claude Pro, Max, Team, Enterprise, or Console account. The Edit -rule startup warning in Troubleshooting requires 2.1.210 or later. 1. Seed a demo repo Three modules log with string concatenation. A shared logger exists but nobody calls it. The test encodes the target state, so it fails until the refactor lands. mkdir refactor-demo && cd refactor-demo && git init -q mkdir -p src/lib test echo '{"name":"refactor-demo","type":"module","private":true}' package.json cat src/lib/logger.js <<'EOF' export const records = ; export const logger = { info event, fields = {} { records.push { event, ...fields } ; }, }; EOF cat src/checkout.js <<'EOF' export function checkout cartId, total { console.log "checkout " + cartId + " total=" + total ; return { cartId, total }; } EOF cat src/orders.js <<'EOF' export function placeOrder orderId, cartId { console.log "order placed " + orderId + " from " + cartId ; return { orderId, cartId }; } EOF cat src/users.js <<'EOF' export function createUser userId, email { console.log "user created " + userId + " <" + email + " " ; return { userId, email }; } EOF cat test/logging.test.js <<'EOF' import test from "node:test"; import assert from "node:assert/strict"; import { records } from "../src/lib/logger.js"; import { checkout } from "../src/checkout.js"; import { placeOrder } from "../src/orders.js"; import { createUser } from "../src/users.js"; test "every module logs through the shared logger", = { records.length = 0; checkout "cart 1", 42 ; placeOrder "ord 1", "cart 1" ; createUser "u 1", "a@example.com" ; assert.deepEqual records.map r = r.event , "checkout", "order placed", "user created" , ; } ; EOF git add -A && git commit -qm "seed" 2. Write the read-only scout Subagents https://code.claude.com/docs/en/sub-agents are Markdown files with YAML frontmatter in .claude/agents/ . Omitting tools inherits everything, so name them explicitly; that's what keeps this one incapable of writing. mkdir -p .claude/agents cat .claude/agents/refactor-scout.md <<'EOF' --- name: refactor-scout description: Inventories every call site for a refactor and returns a file-by-file plan. Read-only. tools: Read, Grep, Glob model: sonnet color: cyan --- You map refactors. You never edit files. Given a scope glob and a goal: 1. Glob the scope, then Grep for every construct the goal touches. 2. Read each match with enough surrounding lines to see the call shape. 3. Return a Markdown table with columns: file, line, current call, replacement, risk. 4. End with a line starting BLOCKERS: listing anything ambiguous — dynamic call sites, re-exports, generated files — or BLOCKERS: NONE . Report only. Do not propose a diff. EOF 3. Write the surgeon One file per invocation. Narrow, stateless tasks can run in parallel without stepping on each other, and a bad edit stays one file wide. cat .claude/agents/refactor-surgeon.md <<'EOF' --- name: refactor-surgeon description: Applies one file's rows from an approved refactor plan. Use after refactor-scout. tools: Read, Edit, Grep, Glob disallowedTools: Bash model: sonnet color: orange --- You apply exactly one file's edits from a plan you are handed. - Edit only the file named in your task. If the plan implies changes elsewhere, report that instead of editing. - Keep it minimal: no reformatting, no renames beyond the plan, no new dependencies. - Add whatever import the replacement needs, with the correct relative path. - Finish by reporting the lines you changed and anything you skipped. EOF Subagents start with no conversation history, so the surgeon only knows what the orchestrator pastes into its task. Hand it the plan rows verbatim. 4. Fence off the blast radius Permission rules https://code.claude.com/docs/en/permissions apply to subagents too, and deny beats allow. Denying Edit test/ turns the test suite into an oracle the agents can't rewrite. cat .claude/settings.json <<'EOF' { "permissions": { "allow": "Bash node --test ", "Bash git status ", "Bash git diff ", "Edit src/ " , "deny": "Edit test/ ", "Edit package.json ", "Bash git push " } } EOF Use Edit ... for file rules, never Write ... . Only Edit and Read rules are consulted by file permission checks. 5. Add the /refactor slash command A directory under .claude/skills/ becomes a slash command https://code.claude.com/docs/en/slash-commands named after the directory. The cmd syntax runs a shell command before Claude sees the file and injects the output. mkdir -p .claude/skills/refactor cat .claude/skills/refactor/SKILL.md <<'EOF' --- name: refactor description: Scout a multi-file refactor, then apply it one file at a time argument-hint: " scope-glob goal " disable-model-invocation: true allowed-tools: Bash git status Bash git diff Bash node --test Read Grep Glob --- Working tree before we start git status --short Task Full request: $ARGUMENTS Scope glob: $0 1. Run the refactor-scout subagent over the scope glob with that goal. Wait for it. 2. Print its table. If the BLOCKERS: line is anything but NONE , stop and ask me. 3. Launch one refactor-surgeon subagent per file in the plan, in parallel, each given only that file's rows. They touch disjoint files. 4. Run node --test test/ . Never edit a test to make it pass. 5. Print git diff --stat . EOF disable-model-invocation: true keeps Claude from firing this on its own. You invoke it or nobody does. php flowchart LR C "/refactor" -- M main session M -- |Agent| S "refactor-scout