affaan-m/ECC · error · Error

Legacy Codex sync state requires recovery before reinstall

Error message

Legacy Codex sync state requires recovery before reinstall: ${statePath}

What it means

beginLegacySyncState refuses to start a new legacy Codex sync when a prior sync state file exists at the state path with a status other than 'installed'. The library tracks sync state on disk so it can roll back safely; a non-installed status means a previous run ended mid-flight (started, failed, or partially rolled back) and must be recovered before any new install touches Codex config.

Solutions

  1. Open the state file at the path named in the error (statePath) and inspect its status field
  2. Run the recovery/rollback routine for legacy sync state (the library's recovery path) to restore installed state
  3. Delete or restore the stale state file to a valid 'installed' status only after verifying the Codex home files match expectations
  4. Re-run the sync/reinstall once the state is recovered

Example fix

// before
$ ecc codex sync   // throws: requires recovery before reinstall
// after
$ ecc codex sync --recover   # or remove/recover the stale state file first
$ ecc codex sync
Defensive patterns

Strategy: validation

Validate before calling

const state = readStateIfPresent(statePath);
if (state && state.status !== 'installed') {
  runRecovery(statePath); // recover before reinstalling
}

Type guard

function isRecoverable(state) { return state && state.status !== 'installed'; }

Try / catch

try { beginLegacySyncState(codexHome, options); } catch (e) {
  if (String(e.message).includes('requires recovery')) runRecovery(getStatePath(codexHome));
  else throw e;
}

Prevention

When it happens

Trigger: Calling beginLegacySyncState when readStateIfPresent(statePath) returns a prior state object whose status is not 'installed' — e.g. after a crashed sync, a failed rollback, or a manually edited state file.

Common situations: A previous `codex sync` run was interrupted (Ctrl-C, crash, power loss) leaving status 'in-progress' or 'failed'; running reinstall without running the recovery step; a stale state file left behind after an aborted upgrade.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/fcf13dd9cb248c2f. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/codex-legacy-sync.js:232

    }
    const digest = snapshot
      ? crypto.createHash('sha256').update(snapshot.content).digest('hex')
      : null;
    if (digest !== entry.installedSha256) {
      throw new Error(`Refusing to replace modified legacy Codex artifact: ${filePath}`);
    }
  }
}

function beginLegacySyncState(options) {
  const codexHome = path.resolve(options.codexHome);
  const statePath = getStatePath(codexHome);
  const configPath = path.join(codexHome, 'config.toml');
  const agentsPath = path.join(codexHome, 'AGENTS.md');
  const installedHooksPath = options.installedHooksPath ? path.resolve(options.installedHooksPath) : null;
  const priorState = readStateIfPresent(statePath);
  if (priorState && priorState.status !== 'installed') {
    throw new Error(`Legacy Codex sync state requires recovery before reinstall: ${statePath}`);
  }
  if (priorState) assertInstalledStateUnmodified(priorState);
  const trustedRoots = [...new Set([
    codexHome,
    ...(Array.isArray(priorState?.trustedRoots) ? priorState.trustedRoots : []),
    ...(priorState?.installedHooksPath ? [priorState.installedHooksPath] : []),
    ...(installedHooksPath ? [installedHooksPath] : []),
  ].map(rootPath => path.resolve(rootPath)))];
  const state = priorState ? {
    ...priorState,
    status: 'applying',
    updatedAt: new Date().toISOString(),
    backupDir: options.backupDir ? path.resolve(options.backupDir) : priorState.backupDir,
    installedHooksPath,
    trustedRoots,
    rollbackPreviousHooksPath: options.previousHooksPath || null,
    rollbackPaths: priorState.paths.map(entry => snapshotLegacyPath(path.resolve(entry.path))),
    previousInstalledState: priorState,

View on GitHub (pinned to 8321021c54)