affaan-m/ECC · error · Error

Legacy sync path changed before removal

Error message

Legacy sync path changed before removal: ${filePath}

What it means

removeOpenedRegularFile detects that the quarantined file's dev/ino no longer match the originally opened file, meaning the path was swapped between open and quarantine. The library successfully restores the quarantined file back to its original path, closes the quarantine directory, and then throws this error to report that the removal could not safely proceed because the target changed underneath it. Nothing is deleted; the operation fails cleanly.

Solutions

  1. Simply re-run the command — the path now holds a fresh file and the operation can proceed against it
  2. Identify and stop the concurrent writer (other script instance, watcher, package manager) before re-running
  3. Serialize operations with a lockfile if automation triggers these commands in parallel
  4. Verify final state after re-run (the sync file should contain expected content)

Example fix

// before: parallel racing invocations
Promise.all([uninstallLegacyCodexSync(p), rollbackLegacyCodexSync(p)]);
// after: serialize with a lock
import { lockSync } from 'proper-lockfile';
const release = lockSync(lockDir);
try { await uninstallLegacyCodexSync(p); } finally { release(); }
Defensive patterns

Strategy: retry

Validate before calling

import fs from 'node:fs';
// confirm the path is stable before (re)running
const a = fs.statSync(p);
await new Promise(r => setTimeout(r, 100));
const b = fs.statSync(p);
if (a.dev !== b.dev || a.ino !== b.ino) throw new Error('path keeps changing; find the concurrent writer first');

Try / catch

try {
  await rollbackLegacyCodexSync(p);
} catch (err) {
  if (err.message.startsWith('Legacy sync path changed before removal:')) {
    // safe to retry: nothing was deleted, the path was swapped
    await rollbackLegacyCodexSync(p);
  } else throw err;
}

Prevention

When it happens

Trigger: Between openRegularFileNoFollow and the quarantine step in rollbackLegacyCodexSync/uninstallLegacyCodexSync, another process replaced filePath (delete + recreate, rename-over), so the quarantined node's dev/ino differ from openedStat; the restore back to the original path succeeded, then this error is thrown.

Common situations: Two installs/uninstalls racing; a package manager or dotfile tool rewrote the sync file mid-operation; editor auto-save (atomic rename) replacing the file at the exact moment of uninstall.

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/8921a5bb281ea6b0. Report an issue: GitHub.

Appendix: source

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

  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');
  if (!snapshot) throw new Error(`Legacy Codex sync state not found at ${statePath}`);
  return parseState(snapshot.content, statePath);
}

View on GitHub (pinned to 8321021c54)