Yeachan-Heo/oh-my-codex · critical · Error

canonical_scale_down_config_verification_failed

Error message

canonical_scale_down_config_verification_failed

What it means

After committing the scale-down membership transaction, re-reading the config still shows at least one removable worker present. The committed state does not reflect the removal, indicating a transaction/verification failure.

Source

Thrown at src/team/scaling.ts:2167

                index: worker.index,
                pane_id: worker.pane_id,
                pid: worker.pid ?? null,
              })),
            resource_workers: removableWorkers.map((worker) => ({
              name: worker.name,
              worktree_path: worker.worktree_path,
              worktree_repo_root: worker.worktree_repo_root,
              worktree_branch: worker.worktree_branch,
              worktree_detached: worker.worktree_detached,
              worktree_created: worker.worktree_created,
              team_state_root: worker.team_state_root,
            })),
          };
          await writeAtomic(cleanupDebtPath, JSON.stringify(cleanupDebtBase, null, 2));
          await commitTeamMembershipTaskTransaction(sanitized, leaderCwd, membershipTransaction);
          const committed = await readTeamConfig(sanitized, leaderCwd);
          if (!committed || committed.workers.some((worker) => removableWorkerNames.has(worker.name))) {
            throw new Error('canonical_scale_down_config_verification_failed');
          }
          config.workers = committed.workers;
          config.worker_count = committed.worker_count;
          for (const task of reconciledTasks) {
            const reconciled = await readTask(sanitized, task.id, leaderCwd);
            if (reconciled && (removableWorkerNames.has(reconciled.owner ?? '') || removableWorkerNames.has(reconciled.claim?.owner ?? ''))) {
              throw new Error(`canonical_scale_down_task_verification_failed:${task.id}`);
            }
          }

          // PID-less rollback records are cleanup debt, not effect authority. A
          // fresh gone proof may resolve one, but a live or unavailable proof
          // must remain debt rather than letting teardown adopt its current PID.
          const pinnedPaneWorkers = removableWorkers.filter((worker): worker is WorkerInfo & {
            pane_id: string;
            pid: number;
          } => (
            typeof worker.pane_id === 'string'

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Check for concurrent processes writing team config and stop them
  2. Inspect/clear .membership-task-transaction.json and re-run scaleDown to reconcile
  3. Read the config directly to confirm current membership, then retry removal for remaining workers
Defensive patterns

Strategy: retry

Try / catch

catch (e) {
  if ((e as Error).message === 'canonical_scale_down_config_verification_failed') {
    await stopConcurrentWriters(team);
    return retryScaleDownOnce();
  }
  throw e;
}

Prevention

When it happens

Trigger: scaleDown commits membershipTransaction then readTeamConfig returns a config containing a worker in removableWorkerNames.

Common situations: Another writer mutated the config between commit and verification; transaction journal replay overwriting the commit; filesystem caching or torn writes on the config file.

Related errors


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