Yeachan-Heo/oh-my-codex · error · Error
worktree_rollback_failed
worktree_rollback_failed
Error message
worktree_rollback_failed:${errors.join(' | ')} What it means
rollbackProvisionedWorktrees aggregates every individual cleanup failure (worktree removal, branch deletion, etc.) into an errors array; if any entry failed, it throws worktree_rollback_failed with all messages joined by ' | '. Callers are startTeam, shutdownTeam, rollbackScaleUp, and cleanupScaleDownResources — i.e. this fires during teardown/compensation paths, and individual component errors (like delete_branch:<name>:<stderr>) are collected rather than failing fast.
Source
Thrown at src/team/worktree.ts:547
const stillCheckedOut = hasBranchInUse(entriesAfterRemove, result.branchName, result.worktreePath);
if (stillCheckedOut) continue;
try {
await execFilePromise('git', ['branch', '-D', result.branchName], {
cwd: result.repoRoot,
encoding: 'utf-8',
});
} catch (err: unknown) {
if (branchExists(result.repoRoot, result.branchName)) {
const stderr = ((err as Record<string, unknown>).stderr as string ?? '').trim();
const exitCode = (err as Record<string, unknown>).code;
errors.push(`delete_branch:${result.branchName}:${stderr || `exit_${exitCode}`}`);
}
}
}
if (errors.length > 0) {
throw new Error(`worktree_rollback_failed:${errors.join(' | ')}`);
}
}
export async function removeWorktreeForce(repoRoot: string, worktreePath: string): Promise<void> {
await execFilePromise('git', ['worktree', 'remove', '--force', worktreePath], {
cwd: repoRoot,
encoding: 'utf-8',
});
}
View on GitHub (pinned to 3ad79a8a6f)
Solutions
- Read each pipe-separated segment: delete_branch:<branch>:<stderr> tells you which git op and why; fix that underlying condition first
- Ensure all processes with cwd inside the worktrees are terminated/killed before calling shutdown/rollback
- Retry the rollback after manual cleanup: `git worktree remove --force <path>` and `git branch -D <branch>` for the listed items
- Serialize rollback with scale-down cleanup (a lock around rollbackScaleUp/cleanupScaleDownResources) to avoid double-free of the same worktree
Example fix
// before
await shutdownTeam(team); // throws worktree_rollback_failed: delete_branch:foo:...
// after
await stopAllAgentProcesses(team); // release file handles first
execSync('git worktree remove --force .worktrees/foo', { cwd: repoRoot });
await shutdownTeam(team); Defensive patterns
Strategy: try-catch
Try / catch
try {
await rollbackProvisionedWorktrees(repoRoot, worktrees);
} catch (e) {
if (e instanceof Error && e.message.startsWith('worktree_rollback_failed:')) {
const parts = e.message.slice('worktree_rollback_failed:'.length).split(' | ');
// each part: <op>:<target>:<stderr> — remediate then retry rollback for remaining items
}
throw e;
} Prevention
- Kill all processes with cwd inside worktrees before teardown
- Idempotent rollback: tolerate partially-cleaned state on retry
- Avoid running shutdownTeam and cleanupScaleDownResources concurrently for the same resources
When it happens
Trigger: Calling rollbackProvisionedWorktrees when at least one git operation in the rollback loop fails — e.g. `git branch -D` refuses because the branch is checked out elsewhere, or worktree remove fails because a process holds files open in the worktree directory.
Common situations: Teardown racing with still-running agent processes that hold files open in worktrees (Windows file locks especially); branches recreated or re-checked-out between provisioning and rollback; partial cleanup where an earlier failure leaves state the later steps depend on; shutdown during a concurrent scale-down already cleaning the same resources.
Related errors
- worktree_prune_failed
- (result.stderr || '').trim() || `git ${args.join(' ')} faile
- (result.stderr || '').trim() || `git status failed for ${wor
- autoresearch_reset_requires_clean_worktree
- stale_team_artifacts:${teamName}:${worktreePaths.length}_wor
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/addc3d6c0f06871e.
Report an issue: GitHub.