affaan-m/ECC · error · Error

Legacy sync path changed before removal; preserved replaceme

Error message

Legacy sync path changed before removal; preserved replacement at ${quarantinePath}

What it means

removeOpenedRegularFile deletes safely by first hardlinking the opened file into a quarantine temp dir, unlinking the original, then comparing dev/ino of the opened descriptor against the quarantined copy. If they differ, the path was replaced between open and removal (error 98's situation) — and in this branch the attempted restore (linking the quarantined original back to the path) also failed. The replacement file currently sitting at the path is left intact, and the original content is preserved at the reported quarantine path so nothing is lost.

Source

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

}

function removeOpenedRegularFile(filePath, opened) {
  const quarantineDir = fs.mkdtempSync(path.join(path.dirname(filePath), '.ecc-remove-'));
  const quarantinePath = path.join(quarantineDir, path.basename(filePath));
  fs.renameSync(filePath, quarantinePath);
  const quarantined = openRegularFileNoFollow(quarantinePath);
  const openedStat = fs.fstatSync(opened.descriptor, { bigint: true });
  const quarantinedStat = fs.fstatSync(quarantined.descriptor, { bigint: true });
  fs.closeSync(quarantined.descriptor);
  fs.closeSync(opened.descriptor);
  opened.descriptor = null;
  if (quarantinedStat.dev !== openedStat.dev || quarantinedStat.ino !== openedStat.ino) {
    try {
      fs.linkSync(quarantinePath, filePath);
      fs.unlinkSync(quarantinePath);
      fs.rmdirSync(quarantineDir);
    } catch (_restoreError) {
      throw new Error(
        `Legacy sync path changed before removal; preserved replacement at ${quarantinePath}`
      );
    }
    throw new Error(`Legacy sync path changed before removal: ${filePath}`);
  }
  fs.unlinkSync(quarantinePath);
  fs.rmdirSync(quarantineDir);
}

function atomicWriteJson(filePath, value) {
  fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
  const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
  fs.writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
  fs.renameSync(tempPath, filePath);
}

function readState(statePath) {
  const snapshot = readRegularFileNoFollow(statePath, 'utf8');

View on GitHub (pinned to 06c5e118c4)

Solutions

  1. Copy your data out of the preserved original at the reported quarantine path, then decide which version should win.
  2. Delete the quarantine directory (.ecc-remove-*) once recovered.
  3. Eliminate the concurrent writer (single sync job, locking) so the swap cannot recur.

Example fix

# before
# Error: ...preserved replacement at ~/.codex/.ecc-remove-XXXXXX/state.json

# after — recover the preserved original, drop quarantine
$ cp ~/.codex/.ecc-remove-*/state.json ~/state-recovered.json
$ rm -rf ~/.codex/.ecc-remove-*
Defensive patterns

Strategy: fallback

Try / catch

try { removeLegacyState(statePath); } catch (e) {
  const m = e.message.match(/preserved replacement at (.+)$/);
  if (m) {
    console.error(`Original preserved at ${m[1]} — recovering content, replacement kept at ${statePath}`);
    fs.copyFileSync(m[1], backupPath);
    fs.rmSync(path.dirname(m[1]), { recursive: true, force: true });
  } else throw e;
}

Prevention

When it happens

Trigger: A concurrent process renames a new file over the state path while removal is in flight, and simultaneously the state directory's permissions or another failure prevents the quarantine hardlink from being linked back.

Common situations: Parallel sync processes fighting over the same state file on a permission-restricted directory; cleanup daemons recreating state files mid-delete.

Related errors


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