affaan-m/ECC · error · Error

Legacy sync path changed while opening: ${filePath}

Error message

Legacy sync path changed while opening: ${filePath}

What it means

The state file was opened successfully with O_NOFOLLOW, but the subsequent lstat on the path returned ENOENT — the directory entry vanished between the open() and the lstat(). Because the module verifies the opened descriptor against the path (dev/ino identity) to prevent time-of-check/time-of-use substitution, an entry that disappears mid-flight cannot be verified, so it aborts with a descriptor-closing cleanup.

Source

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

      } catch (lstatError) {
        if (lstatError.code === 'ENOENT') return null;
        throw lstatError;
      }
      throw error;
    }
    if (error.code === 'ELOOP') {
      throw new Error(`Refusing to manage non-regular legacy sync path: ${filePath}`);
    }
    throw error;
  }
  const descriptorStat = fs.fstatSync(descriptor, { bigint: true });
  let finalPathStat;
  try {
    finalPathStat = fs.lstatSync(filePath, { bigint: true });
  } catch (error) {
    fs.closeSync(descriptor);
    if (error.code === 'ENOENT') {
      throw new Error(`Legacy sync path changed while opening: ${filePath}`);
    }
    throw error;
  }
  if (
    !descriptorStat.isFile()
    || !finalPathStat.isFile()
    || finalPathStat.isSymbolicLink()
    || descriptorStat.dev !== finalPathStat.dev
    || descriptorStat.ino !== finalPathStat.ino
    || descriptorStat.nlink !== 1n
    || finalPathStat.nlink !== 1n
  ) {
    fs.closeSync(descriptor);
    throw new Error(`Refusing to manage non-regular legacy sync path: ${filePath}`);
  }
  return { descriptor, stat: fs.fstatSync(descriptor) };
}

View on GitHub (pinned to 06c5e118c4)

Solutions

  1. Re-run the command — this is a transient race and normally succeeds when only one writer is active.
  2. Serialize sync operations (lock file, single CI job, or sequential shell steps) so only one process manages the state file.
  3. Exclude the state directory from antivirus/backup real-time scanning that may unlink and recreate files.

Example fix

# before: two racing jobs
$ ecc codex sync & ecc codex sync &
# Error: Legacy sync path changed while opening

# after: single writer
$ ecc codex sync
Defensive patterns

Strategy: retry

Try / catch

function withRetry(fn, { attempts = 3, isTransient = (e) => /changed while opening/.test(e.message) } = {}) {
  for (let i = 0; i < attempts; i++) {
    try { return fn(); } catch (e) { if (i === attempts - 1 || !isTransient(e)) throw e; }
  }
}

Prevention

When it happens

Trigger: Two sync operations racing on the same state file (one removes/renames it while another opens it); aggressive antivirus/backup/cleanup tools unlinking the file at that moment.

Common situations: Running `ecc codex sync` in parallel shells or CI jobs; editors with file watchers that recreate state files; overlapping hook executions.

Related errors


AI-assisted analysis of affaan-m/ECC@06c5e118c4 (2026-08-18). Data as JSON: /api/errors/a29839056da388aa. Report an issue: GitHub.