workflowMigrations at Scale9 min read
Fleet Migrations
Once the codemod has eaten the mechanical work, what remains is too varied for a transform and too large for one session. That residue is the job for a fleet. The hard part is not spawning agents. It is cutting units that can each merge alone.
How to run the judgement-heavy part of a large Frontend migration as a fan-out. /batch splits one change into 5 to 30 worktree-isolated subagents that each publish their own change. A dynamic workflow codifies the same fan-out as a script you can read and rerun. Neither works unless every unit carries its own machine-checkable proof.
On this page10 parts
Start After the Codemod
Codemod-First Migrations answers what to do with a migration: sort it into mechanical, patterned and judgement, turn the mechanical bucket into a program, and gate every file. This entry picks up where it leaves the patterned bucket, the files a transform can scaffold but cannot finish, and answers one narrower question: how do you run that bucket in parallel without the agents colliding or the review collapsing?
If you have not done the triage, do it first. A fleet pointed at mechanical work is the most expensive way available to get an inconsistent result. A fleet is also not a team. Agent Teams is for interdependent work where the workers need to talk. Migration units should need nothing from each other, and if they do, they are cut wrong.
The Unit Is the Whole Method
The decomposition is the actual work, and it is a dependency-ordered cut rather than an arbitrary file split.
- Migrate the shared layer first, serially.Design tokens, base primitives, the theme provider, the utility layer. Every other unit depends on it, and none of them can be verified until it is stable. Merge it before the fan-out starts, because that is what the worktrees will branch from.
- Then cut leaves.One route folder, one component family, one feature directory per unit. The test is simple: could this unit merge on its own, today, with no other unit merging first? If not, it is not a unit yet.
- Size each unit to one reviewable pull request.Not one file, which buries reviewers in noise. Not one package, which reproduces the original problem at a smaller scale.
- Write the convention down before anything runs.One reference implementation plus a short rules file, committed to the repository. Every agent reads the same one, so the last unit looks like the first.
Two Ways to Run the Fan-Out
/batch is the packaged version and the right place to start. It is a bundled skill: it researches the codebase, decomposes the work into 5 to 30 independent units, and presents a plan. Once you approve, it spawns one background subagent per unit, each in its own git worktree, and each one implements its unit, runs the tests and publishes its change, normally as a pull request.
/batch migrate src/components from styled-components to TailwindApproving the plan is the checkpoint that matters. If it cuts the work in the wrong places, fix it there, not in review.
A dynamic workflow is the version you keep. Claude writes a JavaScript script that holds the orchestration, and a runtime executes it in the background while your session stays responsive. Intermediate results live in script variables instead of a context window. Dynamic workflows are available on all paid plans and through the API and the cloud providers, and on Pro you turn them on from the Dynamic workflows row in /config.
use a workflow to migrate every component directory under src/components/off styled-components, working on each one in its own isolated copyWhat it produces is a file you can read, diff, edit and rerun:
export const meta = { name: 'migrate-styled-to-tailwind', description: 'Migrate component directories off styled-components',};const found = await agent( 'List every directory under src/components/ that still imports styled-components.', { schema: { type: 'object', required: ['dirs'], properties: { dirs: { type: 'array', items: { type: 'string' } } }, }, });const results = await pipeline(found.dirs, (dir) => agent( `Migrate ${dir} off styled-components following docs/migration-rules.md. Run the typecheck and the tests for ${dir}. Open a pull request only if both pass. Report the outcome and the test output.`, { label: dir, isolation: 'worktree' } ));return results.filter(Boolean);Migration and verification sit in one agent on purpose. A second verifying agent would not be in the first one's worktree, so it would check the wrong copy of the code.
Press s on the run in /workflows and it saves as a command, in .claude/workflows/ for the repository or ~/.claude/workflows/ for you. Pick by what you want to keep: /batch leaves you the pull requests, and a workflow leaves you the pull requests and the orchestration.
Outside Claude Code, GitHub Copilot CLI has /fleet, where the main agent decides whether a prompt can be split and runs independent subagents in parallel. The difference that matters here is isolation: GitHub's own write-up says those subagents share a filesystem with no file locking, so the last write to a file silently wins, and it tells you to partition files in the prompt. The cut above is doing even more work there. GitHub also has its own dynamic workflows, defined in code in Copilot extensions and in public preview at the time of writing.
Isolation That Actually Holds
Parallel agents editing one checkout is the failure everyone hits first. Worktrees remove it structurally: each agent gets its own working directory and branch, sharing the repository's history and remote.
Make it permanent for a migration subagent with one line of frontmatter:
---name: migratordescription: Applies one unit of a codebase-wide migrationisolation: worktree---Migrate the assigned unit following docs/migration-rules.md, run thetypecheck and the tests for the touched directory, then report the result.Claude Code enforces the boundary with checks rather than trusting the agent. From inside a worktree it blocks edits targeting the main checkout, blocks commands whose working directory resolves there, blocks git redirected back through -C, --git-dir, GIT_DIR or a cd, and refuses commands it cannot verify stay inside. Know the edge: those checks read paths and command text, so a plain cp or shell redirect into the main checkout is not caught by them. Your permissions and sandbox settings are what cover that.
In a monorepo, a full checkout per agent is what makes the fan-out slow and fills the disk. Two settings fix it:
{ "worktree": { "sparsePaths": [".claude", "packages/web", "packages/design-system"], "symlinkDirectories": ["node_modules"] }}sparsePaths writes only those directories plus root-level files, and symlinkDirectories points each worktree's node_modules back at the main copy instead of duplicating it. Every worktree in a session shares the same sparsePaths, so list every directory any unit will need.
Two more that bite in practice. Worktrees branch from the default branch on the remote by default, which is why the shared layer has to be merged first. Set worktree.baseRef to "head" only when units genuinely have to build on unpushed work. And a worktree is a fresh checkout, so a gitignored .env your dev server needs is not there until you list it in .worktreeinclude.
The Gate Has to Run in the Unit
The per-file gate from Codemod-First Migrations still applies. What the fleet adds is a constraint on it: the gate has to run against one directory, inside one worktree, with no other unit merged. If your test suite cannot do that, fix it before the fan-out rather than during it, because it is what makes every unit independently mergeable. Include the visual check from The Browser Verification Loop for any unit that touches rendering, and identify pre-existing failures up front so no agent spends its run on them.
For units nobody needs to watch, run them headless from a checkout the CI job owns, and let the script run the gate rather than the agent:
claude --bare -p "Migrate $UNIT following docs/migration-rules.md." \ --allowedTools "Read,Edit,Bash" \ --output-format json > "$RUN_LOG" \ && npm run typecheck \ && npx vitest run "$UNIT"A zero exit from Claude Code means the run completed, not that the migration is right, which is why the checks follow it. --bare skips auto-discovery of hooks, skills, plugins, MCP servers and CLAUDE.md, so the run is the same on every machine, and it does not use your subscription login, so set ANTHROPIC_API_KEY. Load back what the unit needs, explicitly, with --settings, --append-system-prompt-file or --plugin-dir.
Know the Ceilings Before You Plan the Cut
A workflow run has up to 16 concurrent agents by default, fewer when Claude Code has fewer CPUs available, and 1,000 agents in total. A single pipeline() call accepts up to 4,096 items. Extra agents queue, so concurrency sets the pace rather than the size of the job.
The agent count Claude aims for when it writes a script comes from a size guideline, which is advice rather than a cap: small is fewer than 5 agents, medium fewer than 10, large fewer than 50. The default is medium, or small on a Pro plan on recent versions, so a real migration usually needs you to say how many units you want or change the setting. A run that schedules more than 25 agents or projects past 1.5 million tokens shows an advisory Large workflow warning, and a guideline you pick yourself replaces the agent threshold. Before you commit, run a small slice and read the token usage per agent in /workflows.
Failure is the expensive path. On a relaunch, completed agents return their saved results, but a failed agent runs again and so does every agent the script started after it, even ones that finished. Editing a prompt has the same effect from that agent onward. Keep the work list stable between runs, and treat a large fan-out as several smaller runs over a list rather than one script you hope never fails.
Why This Matters for Senior Teams
- It reprices the part codemods cannot reach.The transform handled the cheap majority. The patterned bucket is what keeps a migration open for quarters, and once its units are independent and self-verifying, the schedule is set by review throughput rather than by how long one engineer can hold a convention in their head.
- The diff stays reviewable.A set of scoped pull requests, each with its own passing gate, is something a team can merge. One enormous branch is not, regardless of who or what wrote it. The Reviewable Stack covers the review side of that bargain.
- The cut is an artifact worth keeping.The plan
/batchshows you, or the script a workflow saves, is a reusable description of how your codebase is shaped. The next migration starts from it. - It surfaces the real bottleneck early.Teams find out fast that they can produce migrated code faster than they can review it. Better to meet that constraint with a plan in the first week than by surprise months in.
Where It Fits in the Stack
- Golden-PR Evals tell you whether to start.Run the first few units by hand, score them, fix the rules file, then fan out. Do not debug your convention across a whole fleet.
- Spec-Driven Development writes the cut.The rules file and the reference implementation are the spec.
- The Autonomy Ladder sets the envelope.Workflow agents use your permission rules, and an agent's permission prompt pauses the run. Add what the units need to your allow rules before you start, and keep the deny rules as the boundary rather than the prompt.
- Plugins ship it to the rest of the org.Put the saved script in
workflows/at the plugin root and the migration runs as/your-plugin:migrate-xfor every team.
When Not to Use It
- The work is still mechanical.Write the codemod. That entry explains why, and nothing here overrides it.
- The change is not actually decomposable.If one unit cannot merge without another, you have a sequence, and a fan-out produces conflicting branches instead of pull requests.
- You cannot verify a unit in isolation.Without a per-unit gate this is a machine for generating review debt.
- Every site needs a decision rather than a rule.That is the judgement bucket. It belongs in a session with a human in it.
- There are only a handful of units.Do them in one session. The coordination costs more than it returns.
Source
- Orchestrate subagents at scale with dynamic workflowscode.claude.com (opens in a new tab)
- Claude Code commands: `/batch`code.claude.com (opens in a new tab)
- Run parallel sessions with worktreescode.claude.com (opens in a new tab)
- Set up Claude Code in a monorepo or large codebasecode.claude.com (opens in a new tab)
- Run Claude Code programmaticallycode.claude.com (opens in a new tab)
- Delegating and parallelizing work with agents, GitHub Docsdocs.github.com (opens in a new tab)
- Run multiple agents at once with /fleet in Copilot CLI, The GitHub Bloggithub.blog (opens in a new tab)
- Dynamic workflows, GitHub Docsdocs.github.com (opens in a new tab)
