affaan-m/ECC · error

rollback failed: ${errors.join('; ')}

Error message

rollback failed: ${errors.join('; ')}

What it means

Thrown by rollbackCreatedResources() at the end of an executePlan rollback after one or more cleanup steps (tmux kill-session, git worktree remove, git worktree prune, git branch -D) failed. The function collects all errors into an array and throws a single aggregate so the caller sees every failure, not just the first. The message is the semicolon-joined list of underlying error.message strings.

Source

Thrown at scripts/lib/tmux-worktree-orchestrator.js:478

    } catch (error) {
      errors.push(error.message);
    }

    if (branchExistsImpl(plan.repoRoot, workerPlan.branchName)) {
      try {
        runCommandImpl('git', ['branch', '-D', workerPlan.branchName], { cwd: plan.repoRoot });
      } catch (error) {
        errors.push(error.message);
      }
    }
  }

  if (createdState.removeCoordinationDir && fs.existsSync(plan.coordinationDir)) {
    fs.rmSync(plan.coordinationDir, { force: true, recursive: true });
  }

  if (errors.length > 0) {
    throw new Error(`rollback failed: ${errors.join('; ')}`);
  }
}

function executePlan(plan, runtime = {}) {
  const spawnSyncImpl = runtime.spawnSync || spawnSync;
  const runCommandImpl = runtime.runCommand || runCommand;
  const materializePlanImpl = runtime.materializePlan || materializePlan;
  const overlaySeedPathsImpl = runtime.overlaySeedPaths || overlaySeedPaths;
  const cleanupExistingImpl = runtime.cleanupExisting || cleanupExisting;
  const rollbackCreatedResourcesImpl = runtime.rollbackCreatedResources || rollbackCreatedResources;
  const createdState = {
    workerPlans: [],
    sessionCreated: false,
    removeCoordinationDir: !fs.existsSync(plan.coordinationDir)
  };

  runCommandImpl('git', ['rev-parse', '--is-inside-work-tree'], { cwd: plan.repoRoot });
  runCommandImpl('tmux', ['-V']);

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Read each segment of the joined message — they correspond to the underlying git/tmux error strings, which usually tell you the exact ref or path.
  2. Inspect remaining state: tmux ls, git worktree list, git branch --list 'orchestrator-*', and the .orchestration directory.
  3. Run cleanupExisting manually (or re-run executePlan with replaceExisting: true) to converge on a clean state.
  4. If rollback consistently fails on a specific step (e.g. branch -D on a checked-out branch), switch the main worktree off that branch before re-running.

Example fix

// before
// executePlan threw, then rollback threw: rollback failed: fatal: ... ; fatal: ...
// state is now unknown

// after
const orchestrator = require('scripts/lib/tmux-worktree-orchestrator');
// re-run with replaceExisting to converge
orchestrator.executePlan(
  orchestrator.buildOrchestrationPlan({ ...config, replaceExisting: true }),
);
// pre-clean manually if replaceExisting still struggles:
//   git worktree prune --expire now
//   git branch -D $(git branch --list 'orchestrator-*')
Defensive patterns

Strategy: try-catch

Validate before calling

function convergeOrchestratorState(repoRoot, sessionName, branchGlob) {
  const { runCommand, commandSucceeds } = require('scripts/lib/tmux-worktree-orchestrator');
  runCommand('git', ['worktree', 'prune', '--expire', 'now'], { cwd: repoRoot });
  if (commandSucceeds('tmux', ['has-session', '-t', sessionName])) {
    runCommand('tmux', ['kill-session', '-t', sessionName]);
  }
  runCommand('git', ['branch', '-D', branchGlob], { cwd: repoRoot }).status;
}

// call before re-running executePlan to avoid rollback failures

Type guard

function tmuxSessionExists(name) {
  return commandSucceeds('tmux', ['has-session', '-t', name]);
}

function gitBranchExists(repoRoot, name) {
  return commandSucceeds('git', ['show-ref', '--verify', '--quiet', `refs/heads/${name}`], { cwd: repoRoot });
}

Try / catch

try {
  executePlan(plan);
} catch (error) {
  if (/rollback failed/.test(error.message)) {
    // rollback itself failed: surface each underlying error and reconverge manually
    console.error(error.message);
    convergeOrchestratorState(plan.repoRoot, plan.sessionName, 'orchestrator-*');
    return;
  }
  throw error;
}

Prevention

When it happens

Trigger: executePlan fails partway and rollback runs; tmux kill-session fails because the session was never created or was already killed; git worktree remove fails because the worktree was already deleted out-of-band; git branch -D fails because the branch is checked out in the main worktree; multiple workers each contribute a cleanup error.

Common situations: The original executePlan failure left the workspace in a state the rollback did not anticipate (e.g. a worktree directory removed manually but the git metadata still present); a prior cleanup already deleted the session; race with another process touching the same branches; permissions issue on the coordination directory.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/bef92c22a3aa56ec. Report an issue: GitHub.