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
- Simply re-run the command — the path now holds a fresh file and the operation can proceed against it
- Identify and stop the concurrent writer (other script instance, watcher, package manager) before re-running
- Serialize operations with a lockfile if automation triggers these commands in parallel
- 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
- Retry once — the library restores state before throwing, so a second run usually succeeds
- Eliminate concurrent writers (other script runs, package managers, auto-save renames)
- Use a lockfile around all legacy-sync operations
- After success, verify the sync file content matches expectations
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
- artifact changed before reading
- artifact changed during reading
- capsule.lock_lost
- Invalid ECC repo root: unreadable package.json at
- Legacy sync path changed before removal; preserved…
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)