Claude Code Multi-Repository Agents: Coordinating Changes Across Monorepo Packages with Subagents
How teams use Git worktrees and isolated subagents to coordinate breaking changes across monorepo packages without merge conflicts or context collision.
Most monorepo coordination failures stem from agents stepping on each other's work. When you deploy multiple Claude Code agents to update dependent packages in parallel, the default execution model produces collisions: agents read stale state, overwrite each other's changes, and leave the repository in a half-applied configuration that breaks the build. Teams discover this the hard way when a seemingly simple "update the shared types package and its three consumers" task results in a broken CI pipeline and two hours of manual reconciliation.
The structural fix is isolation through Git worktrees paired with dependency-aware orchestration. Agents work in separate filesystem copies of the repository, coordinated by a conductor that enforces ordering constraints and merges results atomically. This pattern eliminates race conditions while preserving parallelism for independent package updates.
flowchart LR
A("Parallel agents start") --> B("Agent 1 reads stale types")
A --> C("Agent 2 updates types")
B --> D("Agent 1 overwrites types")
D --> E("broken build")
style E stroke:#ef4444,fill:#450a0a,color:#fca5a5
The correct approach spawns agents in isolated worktrees with explicit dependency ordering. A root package update completes and merges before its consumers start. The build stays green throughout the process.
flowchart LR
A("Parallel agents start") --> B("Agent 1: isolated worktree")
A --> C("Agent 2: isolated worktree")
B --> D("changes merge atomically")
C --> D
D --> E("build stays green")
style E stroke:#34d399,fill:#0b3b2e,color:#d1fae5
Key Takeaways
- Git worktrees give each subagent an isolated filesystem copy of the repository, preventing file-level race conditions when multiple agents modify the same monorepo in parallel.
- Dependency-aware orchestration sequences package updates so foundational changes (shared types, core utilities) complete and merge before dependent consumers begin work.
- The
isolation: worktreeconfiguration in Claude Code creates a new worktree per subagent, enabling true parallelism for independent packages while the conductor enforces ordering for related changes. - Integration testing runs after all worktree changes merge, validating cross-package compatibility before the conductor signals completion.
- Sequential single-agent execution is cheaper and simpler for small monorepos (fewer than five packages) or when all changes touch interdependent files.
Understanding Subagent Isolation with Git Worktrees
Git worktrees solve the fundamental problem of concurrent filesystem access. When two agents modify the same file without isolation, the second write wins and the first agent's changes disappear. Worktrees create separate working directories pointing to the same Git repository, so each agent sees its own copy of the codebase.
The isolation model works through filesystem separation, not Git branches. Each worktree lives in a distinct directory on disk. An agent modifying packages/types/src/User.ts in worktree A cannot interfere with an agent modifying the same file in worktree B. Both changes exist in parallel until the orchestrator merges them.
flowchart TD
A("main repository") --> B("worktree A: packages/types")
A --> C("worktree B: packages/api")
A --> D("worktree C: packages/ui")
B --> E("Agent 1 modifies User.ts")
C --> F("Agent 2 modifies routes.ts")
D --> G("Agent 3 modifies Button.tsx")
E --> H("merge to main")
F --> H
G --> H
style A stroke:#7c9cf0,fill:#142544,color:#eaf2ff
style H stroke:#c084fc,fill:#3b0764,color:#f3e8ff,stroke-width:4px
The worktree lifecycle has three phases: creation, agent execution, and cleanup. Creation happens before the agent starts. The orchestrator runs git worktree add to spawn a new directory. Agent execution proceeds in isolation within that directory. Cleanup removes the worktree after changes merge back to the main branch.
This approach introduces overhead. Each worktree consumes disk space proportional to the repository size. A 500MB monorepo with five active worktrees requires 2.5GB of disk. The overhead matters for large repositories on resource-constrained CI runners. Teams working with repositories exceeding 1GB typically implement worktree pooling to reuse directories across agent runs.
The dependency graph determines which agents can run in parallel. Independent packages use concurrent worktrees. Dependent packages run sequentially, with the dependency merging before its consumer starts. The orchestrator maintains this ordering through explicit wait conditions in the coordination logic.
Coordinating Package Changes Across a Monorepo
The coordination pattern starts with a dependency map. The orchestrator needs to know which packages consume which other packages to enforce correct sequencing. This information comes from parsing package.json files across the monorepo.
interface PackageDependencyGraph {
packages: Map<string, PackageNode>;
edges: Map<string, Set<string>>; // package -> dependencies
}
interface PackageNode {
name: string;
path: string;
dependencies: string[];
worktreePath?: string;
agentId?: string;
}
class MonorepoOrchestrator {
private graph: PackageDependencyGraph;
private completedPackages = new Set<string>();
async coordinateUpdate(rootPackage: string): Promise<void> {
const updateOrder = this.topologicalSort(rootPackage);
for (const batch of this.groupByDependencyLevel(updateOrder)) {
const results = await Promise.all(
batch.map(pkg => this.updatePackageInWorktree(pkg))
);
for (const result of results) {
await this.mergeWorktreeChanges(result);
this.completedPackages.add(result.packageName);
}
}
}
private async updatePackageInWorktree(
pkg: PackageNode
): Promise<WorktreeResult> {
const worktreePath = await this.createWorktree(pkg);
const agent = await this.spawnAgent({
isolation: 'worktree',
workingDirectory: worktreePath,
context: {
packageName: pkg.name,
dependencies: Array.from(this.completedPackages),
},
});
const result = await agent.execute();
return { packageName: pkg.name, worktreePath, changes: result };
}
private groupByDependencyLevel(
packages: PackageNode[]
): PackageNode[][] {
const levels: PackageNode[][] = [];
const processed = new Set<string>();
for (const pkg of packages) {
const level = this.calculateDependencyDepth(pkg, processed);
if (!levels[level]) levels[level] = [];
levels[level].push(pkg);
processed.add(pkg.name);
}
return levels;
}
}The groupByDependencyLevel method creates batches of packages that can update in parallel. Packages at the same dependency depth have no interdependencies, so their agents run concurrently. The orchestrator waits for an entire batch to complete before starting the next level.
This pattern handles the common case where updating a shared types package requires coordinated changes across multiple consumers. The types package updates first. After its changes merge, the consumer packages update in parallel within their respective worktrees. The build remains valid throughout because dependent packages never see partially applied type definitions.
The coordination overhead is non-trivial. Each worktree creation takes 2-5 seconds for a medium-sized repository. Merge operations add another 1-3 seconds per package. A monorepo with ten packages requiring sequential updates across three dependency levels incurs 30-90 seconds of orchestration overhead before any agent work begins. Teams with tight CI time budgets often cache worktree directories between runs to amortize this cost.
Subagents vs Agent Teams for Cross-Package Work
Subagents are isolated execution contexts spawned by a parent orchestrator. Agent teams are peer agents coordinating through shared state. The difference is control flow and error handling.
flowchart LR
subgraph "Subagent Model"
A1("orchestrator") --> B1("spawn subagent A")
A1 --> C1("spawn subagent B")
B1 --> D1("return result")
C1 --> D1
D1 --> E1("orchestrator merges")
end
subgraph "Agent Team Model"
A2("agent A starts") --> B2("writes to shared state")
A2("agent B starts") --> C2("reads shared state")
B2 --> D2("lock contention")
C2 --> D2
D2 --> E2("manual coordination")
end
style E1 stroke:#34d399,fill:#0b3b2e,color:#d1fae5
style D2 stroke:#fbbf24,fill:#3a2f0b,color:#fef3c7
Subagents report results to a parent that controls merge timing. If subagent A fails, the orchestrator can abort subagent B before it writes anything. Agent teams lack this coordination primitive. When agent A in a team fails, agent B continues unless it explicitly polls for A's status. This creates partial-success states that require manual cleanup.
The subagent model maps naturally to monorepo packages because the orchestrator enforces dependency ordering. The agent team model works better for independent microservices where no coordination is required. Using agent teams for dependent packages introduces the exact race conditions that worktrees were meant to eliminate.
Error propagation differs critically. A subagent failure bubbles up to the orchestrator, which can roll back all worktree changes before they merge. An agent team failure leaves successful agents' changes in place, requiring complex compensation logic to restore consistency.
Context window management also favors subagents. Each subagent receives only the files relevant to its package. An agent team working on the same monorepo must coordinate which agent is responsible for which files, adding cognitive overhead and increasing the chance of context overlap.
The cost difference is measurable. Subagent orchestration adds the overhead of spawning isolated execution contexts. Agent teams avoid this overhead but pay in coordination complexity. For a five-package monorepo update, subagent orchestration takes 45 seconds of setup and merge time. An equivalent agent team implementation requires 120+ lines of coordination code and still fails 15% of the time due to race conditions. The subagent model's upfront cost pays for itself in reliability.
Production Pattern: The Dependency-Aware Update Strategy
The production-ready orchestration pattern builds on topological sorting with explicit checkpoints and rollback capability. When a package update fails, the orchestrator must undo all changes in that dependency level before attempting recovery.
flowchart LR
A("parse package.json files") --> B("build dependency graph")
B --> C("topological sort")
C --> D("spawn level 0 agents")
D --> E("all level 0 complete?")
E -->|yes| F("merge level 0 changes")
E -->|no| G("rollback level 0 worktrees")
F --> H("spawn level 1 agents")
H --> I("build validates")
style C stroke:#c084fc,fill:#3b0764,color:#f3e8ff,stroke-width:4px
style I stroke:#34d399,fill:#0b3b2e,color:#d1fae5
style G stroke:#fbbf24,fill:#3a2f0b,color:#fef3c7
The dependency graph construction requires parsing workspace configuration. For pnpm workspaces, this means reading pnpm-workspace.yaml and each package's package.json. For Nx monorepos, the project.json files contain the dependency information.
interface DependencyAwareOrchestrator {
checkpoints: Map<number, CheckpointState>;
rollbackHandlers: Map<string, () => Promise<void>>;
}
interface CheckpointState {
level: number;
packages: string[];
worktrees: string[];
mergeCommit?: string;
}
class ProductionOrchestrator implements DependencyAwareOrchestrator {
checkpoints = new Map<number, CheckpointState>();
rollbackHandlers = new Map<string, () => Promise<void>>();
async executeUpdate(rootPackages: string[]): Promise<void> {
const levels = this.buildDependencyLevels(rootPackages);
for (let level = 0; level < levels.length; level++) {
const checkpoint = await this.createCheckpoint(level, levels[level]);
try {
await this.executeLevelWithWorktrees(levels[level]);
await this.mergeLevel(checkpoint);
await this.validateBuild();
} catch (error) {
await this.rollbackToCheckpoint(checkpoint);
throw new OrchestrationError(
`Level ${level} failed: ${error.message}`,
{ level, packages: levels[level] }
);
}
}
}
private async executeLevelWithWorktrees(
packages: PackageNode[]
): Promise<void> {
const worktreeResults = await Promise.allSettled(
packages.map(pkg => this.updateInWorktree(pkg))
);
const failures = worktreeResults.filter(r => r.status === 'rejected');
if (failures.length > 0) {
throw new Error(
`${failures.length} packages failed: ${failures
.map(f => f.reason)
.join(', ')}`
);
}
}
private async rollbackToCheckpoint(
checkpoint: CheckpointState
): Promise<void> {
for (const worktreePath of checkpoint.worktrees) {
await this.cleanupWorktree(worktreePath);
}
if (checkpoint.mergeCommit) {
await this.gitReset(checkpoint.mergeCommit);
}
}
}The checkpoint mechanism captures the repository state before each dependency level begins work. If any package in the level fails, the orchestrator resets to the checkpoint state. This prevents the accumulation of partial changes across failed update attempts.
Build validation runs after each level merges. The orchestrator executes the monorepo's build command and fails fast if compilation errors appear. This catches type mismatches between packages before the next dependency level starts. Teams skip this step at their peril—discovering a broken build after all agents complete requires replaying the entire update sequence.
The pattern scales to monorepos with 50+ packages. At that scale, the dependency graph often contains 8-10 levels. Total orchestration time approaches 15-20 minutes. Teams working with these large repositories implement parallel builds within dependency levels and cache intermediate build artifacts to reduce validation time.
Handling Merge Conflicts and Integration Testing
Merge conflicts occur when two worktrees modify overlapping sections of the same file. The orchestrator cannot resolve these automatically—it needs human input or a conflict resolution strategy.
flowchart LR
A("worktree A changes merge") --> B("worktree B attempts merge")
B --> C("Git detects conflict")
C --> D("orchestrator pauses")
D --> E("conflict resolution strategy")
E -->|automatic| F("apply three-way merge")
E -->|manual| G("notify developer")
F --> H("integration tests run")
G --> H
style C stroke:#fbbf24,fill:#3a2f0b,color:#fef3c7
style H stroke:#c084fc,fill:#3b0764,color:#f3e8ff,stroke-width:4px
The automatic resolution strategy works for non-overlapping changes in the same file. Git's three-way merge handles these cases cleanly. The orchestrator detects successful automatic merges and continues. For true conflicts—where both worktrees modified the same lines—the orchestrator must escalate.
Escalation strategies vary by team policy. Conservative teams halt the entire orchestration and notify a developer. Aggressive teams implement heuristic-based resolution—preferring the worktree from the higher dependency level, for example. The heuristic approach introduces risk. A poorly chosen resolution can break the build in subtle ways that integration tests might not catch.
Integration testing runs after all worktrees merge but before the orchestrator signals completion. The test suite must exercise cross-package interactions. Unit tests within individual packages will pass even if package boundaries are broken. The integration test phase is where incompatible API changes surface.
class IntegrationTestOrchestrator {
async runIntegrationTests(
completedPackages: Set<string>
): Promise<TestResult> {
const affectedTests = this.findAffectedTests(completedPackages);
const testResults = await this.executeTests(affectedTests, {
parallel: true,
bail: true, // Stop on first failure
});
if (!testResults.success) {
throw new IntegrationTestError(
'Cross-package integration tests failed',
{ failures: testResults.failures }
);
}
return testResults;
}
private findAffectedTests(packages: Set<string>): string[] {
const testGraph = this.buildTestDependencyGraph();
const affected = new Set<string>();
for (const pkg of packages) {
const dependentTests = testGraph.get(pkg) || [];
dependentTests.forEach(test => affected.add(test));
}
return Array.from(affected);
}
}The findAffectedTests method limits test execution to the subset of tests that exercise the modified packages. Running the entire test suite after each orchestration run is prohibitively expensive for large monorepos. Affected test detection reduces test time from 20+ minutes to 3-5 minutes for a typical multi-package update.
Test failure handling mirrors the checkpoint rollback strategy. When integration tests fail, the orchestrator rolls back all merged changes from the current orchestration run. This keeps the main branch in a known-good state. The alternative—leaving broken changes in place and fixing forward—creates windows where the repository is unbuildable.
The failure mode here is subtle but expensive. If the orchestrator merges changes incrementally (one package at a time) and runs integration tests after each merge, a test failure five packages in requires rolling back five merges. Batching merges by dependency level reduces rollback scope but increases the blast radius when conflicts occur. Teams tune this tradeoff based on their conflict frequency and test execution time.
When to Skip Worktrees and Use Sequential Agents
Worktree orchestration adds complexity and overhead. For small monorepos or updates confined to a single dependency chain, sequential execution in a single working directory is simpler and faster.
flowchart LR
A("single agent starts") --> B("update types package")
B --> C("commit changes")
C --> D("update API package")
D --> E("commit changes")
E --> F("update UI package")
F --> G("final commit")
style A stroke:#34d399,fill:#0b3b2e,color:#d1fae5
style G stroke:#34d399,fill:#0b3b2e,color:#d1fae5
Sequential execution eliminates merge conflicts entirely. Only one agent modifies the repository at any moment. The agent commits each package update before moving to the next. This approach works well when the total update time is acceptable—typically under 10 minutes for a five-package chain.
The decision boundary depends on parallelism opportunity. A monorepo with three independent packages that all consume a shared types package has high parallelism potential. After updating types, the three consumers can update concurrently in separate worktrees. Total time drops from 15 minutes sequential to 6 minutes parallel.
A monorepo with three packages in a linear dependency chain (A depends on B depends on C) has zero parallelism opportunity. Worktree orchestration adds 30 seconds of overhead per package without reducing total time. Sequential execution finishes faster.
The threshold calculation is straightforward:
interface UpdateStrategy {
parallelismFactor: number; // 0-1, percentage of packages that can run concurrently
worktreeOverheadPerPackage: number; // seconds
averagePackageUpdateTime: number; // seconds
packageCount: number;
}
function shouldUseWorktrees(strategy: UpdateStrategy): boolean {
const sequentialTime =
strategy.packageCount * strategy.averagePackageUpdateTime;
const parallelBatches = Math.ceil(
strategy.packageCount * (1 - strategy.parallelismFactor)
);
const parallelTime =
parallelBatches * strategy.averagePackageUpdateTime +
strategy.packageCount * strategy.worktreeOverheadPerPackage;
return parallelTime < sequentialTime * 0.7; // 30% speedup threshold
}The 30% speedup threshold accounts for the added complexity of worktree orchestration. Saving three minutes on a 10-minute update isn't worth the debugging overhead when orchestration logic fails. Saving 10 minutes on a 30-minute update justifies the complexity.
Teams also skip worktrees when the update touches infrastructure files that affect the entire monorepo. Updating the root tsconfig.json or the shared ESLint configuration requires all packages to rebuild. Parallel execution provides no benefit because the build becomes the bottleneck, not the agent update time.
The pattern selection is not permanent. Teams start with sequential execution for simplicity. When orchestration runs exceed 10-15 minutes, they introduce worktrees for the high-parallelism sections of the dependency graph. The orchestrator can mix strategies—using worktrees for independent package updates and sequential execution for linear chains.
Frequently Asked Questions
How do you handle Git authentication when spawning multiple worktrees?
Git worktrees share the parent repository's authentication configuration automatically. The orchestrator does not need to configure credentials separately for each worktree. The .git directory remains in the parent repository, and worktrees reference it through a gitlink file.
What happens if an agent process crashes mid-execution in a worktree?
The orchestrator detects agent failures through promise rejection when using Promise.allSettled. Crashed agents leave their worktrees in an unknown state. The cleanup phase removes these worktrees before rollback, ensuring no partial changes persist.
Can you reuse worktrees across multiple orchestration runs?
Yes, with careful state management. The orchestrator must reset each worktree to a clean state before reuse, typically by running git reset --hard and git clean -fd. Reusing worktrees saves the 2-5 seconds of creation overhead per package but introduces the risk of state leakage between runs.
How do you prevent disk space exhaustion when orchestrating large monorepos?
Implement a worktree pool with a maximum size limit. When the pool reaches capacity, the orchestrator blocks new worktree creation until an existing worktree completes and returns to the pool. This caps disk usage at a predictable level regardless of the number of concurrent agents.
Does the worktree isolation model work with Yarn/pnpm workspaces?
Fully. Yarn and pnpm resolve dependencies from the root node_modules directory, which remains shared across all worktrees. The isolation applies only to source code files, not to installed dependencies. Each worktree sees the same dependency graph, ensuring consistent build behavior.
Conclusion: Building a Scalable Multi-Agent Workflow
The worktree-based orchestration pattern solves the fundamental coordination problem in monorepo updates. Agents work in isolated filesystem contexts, eliminating race conditions. The orchestrator enforces dependency ordering, ensuring foundational packages update before their consumers. Integration testing catches cross-package incompatibilities before changes reach the main branch.
Teams gain parallelism without sacrificing build stability. A 30-minute sequential update compresses to 10 minutes with proper orchestration. The complexity overhead is real but manageable—200-300 lines of orchestration logic versus hours of manual conflict resolution.
The decision to adopt worktrees depends on monorepo size and dependency structure. Small repositories with linear dependency chains gain little from parallelism. Large repositories with independent package clusters see immediate time savings. Most teams hit the adoption threshold around 10-15 packages.
That covers the essential patterns for monorepo multi-agent coordination. Apply these in production and the difference will be immediate. For more on related coordination patterns, see correlation IDs for AI agents and the initializer coding agent harness.