affaan-m/ECC · error · Error

Invalid legacy Codex sync state at

Error message

Invalid legacy Codex sync state at ${statePath}

What it means

parseState JSON-parses the legacy Codex sync state file and validates its shape: it must contain schema === SCHEMA and an Array paths. If the file exists but is not valid JSON, was written for a different schema version, or is corrupted/truncated, this error is thrown with the offending path. It guards downstream code that iterates state.paths unconditionally.

Solutions

  1. Inspect the state file (cat statePath) and fix or restore the expected { schema, paths: [...] } structure.
  2. Delete the invalid state file and re-run the install so beginLegacySyncState writes a fresh, schema-correct snapshot.
  3. If you intentionally changed SCHEMA, migrate the on-disk file to the new schema value and ensure paths is an array.
  4. Reinstall the matching plugin version so the SCHEMA constant matches the state file that was written with it.

Example fix

// before (hand-edited state.json)
{ "version": 2, "files": ["a"] }
// after
{ "schema": "<SCHEMA value from codex-legacy-sync.js>", "paths": [{ "path": "...", "installedSha256": "..." }] }
// or simply: rm state.json && rerun install
Defensive patterns

Strategy: validation

Validate before calling

import fs from 'node:fs';
function looksLikeValidState(statePath) {
  try {
    const s = JSON.parse(fs.readFileSync(statePath, 'utf8'));
    return typeof s.schema === 'string' && Array.isArray(s.paths);
  } catch {
    return false;
  }
}

Type guard

function isLegacySyncState(value) {
  return (
    typeof value === 'object' && value !== null &&
    'schema' in value && typeof value.schema === 'string' &&
    'paths' in value && Array.isArray(value.paths)
  );
}

Try / catch

let state;
try {
  state = readState(statePath);
} catch (err) {
  if (err instanceof Error && err.message.startsWith('Invalid legacy Codex sync state')) {
    fs.rmSync(statePath, { force: true }); // or archive it
    state = beginLegacySyncState({ codexHome }); // regenerate
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Calling readState or readStateIfPresent on a state file whose parsed JSON has state.schema !== SCHEMA or state.paths is not an array — e.g. hand-edited state, truncated write, state produced by an older/newer plugin version with a different SCHEMA constant.

Common situations: Manually editing or minifying the state file; a partial write from a crash before the atomic temp-file rename; upgrading/downgrading the plugin so the embedded SCHEMA constant no longer matches the on-disk file; state file clobbered by another tool writing its own JSON.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

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

  fs.writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
  fs.renameSync(tempPath, filePath);
}

function readState(statePath) {
  const snapshot = readRegularFileNoFollow(statePath, 'utf8');
  if (!snapshot) throw new Error(`Legacy Codex sync state not found at ${statePath}`);
  return parseState(snapshot.content, statePath);
}

function readStateIfPresent(statePath) {
  const snapshot = readRegularFileNoFollow(statePath, 'utf8');
  return snapshot ? parseState(snapshot.content, statePath) : null;
}

function parseState(content, statePath) {
  const state = JSON.parse(content);
  if (state.schema !== SCHEMA || !Array.isArray(state.paths)) {
    throw new Error(`Invalid legacy Codex sync state at ${statePath}`);
  }
  return state;
}

function hasUnsafeManagedAncestor(filePath, codexHome) {
  const relativePath = path.relative(codexHome, filePath);
  if (relativePath === '' || relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
    return relativePath !== '';
  }
  const segments = relativePath.split(path.sep).slice(0, -1);
  let currentPath = codexHome;
  for (const segment of [null, ...segments]) {
    if (segment !== null) currentPath = path.join(currentPath, segment);
    try {
      const stat = fs.lstatSync(currentPath);
      if (stat.isSymbolicLink() || !stat.isDirectory()) return true;
    } catch (error) {
      if (error.code === 'ENOENT') break;

View on GitHub (pinned to 8321021c54)