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

  1. Read each pipe-separated segment: delete_branch:<branch>:<stderr> tells you which git op and why; fix that underlying condition first
  2. Ensure all processes with cwd inside the worktrees are terminated/killed before calling shutdown/rollback
  3. Retry the rollback after manual cleanup: `git worktree remove --force <path>` and `git branch -D <branch>` for the listed items
  4. 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

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


AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27). Data as JSON: /api/errors/addc3d6c0f06871e. Report an issue: GitHub.