affaan-m/ECC · error · Error
Legacy sync path changed before removal; preserved…
Error message
Legacy sync path changed before removal; preserved replacement at ${quarantinePath} What it means
removeOpenedRegularFile quarantines the opened file (renaming it away) and then verifies the quarantined node still matches the originally opened descriptor's dev/ino before deleting it. If they differ, something replaced the path between open and quarantine; the code attempts to restore the quarantined file to its original path, and if even that restore fails it throws this error while deliberately preserving the replacement file at quarantinePath so nothing is lost.
Solutions
- Inspect the preserved replacement file at the quarantinePath given in the message and decide whether to keep or delete it manually
- Re-run the uninstall/rollback when nothing else touches the path (close editors, watchers, other terminals)
- Remove the re-created file at the original path first, then re-run so restore can succeed
- Serialize script runs with a lock to prevent two concurrent sync operations
Example fix
# before: blind retry npm run <uninstall-command> # after: inspect preserved copy, clean up, then retry cat <quarantineDir>/<quarantined-file> # review preserved replacement rm -rf <quarantineDir> # once reviewed npm run <uninstall-command>
Defensive patterns
Strategy: try-catch
Validate before calling
// ensure no concurrent writer holds the path before calling
import fs from 'node:fs';
const before = fs.statSync(p);
await new Promise(r => setTimeout(r, 50));
const after = fs.statSync(p);
if (before.ino !== after.ino || before.dev !== after.dev) throw new Error('path is being rewritten by another process'); Try / catch
try {
await uninstallLegacyCodexSync(p);
} catch (err) {
if (err.message.includes('preserved replacement at')) {
const quarantinePath = err.message.match(/preserved replacement at (\S+)/)?.[1];
// review and manually resolve the preserved copy before retrying
} else throw err;
} Prevention
- Stop editors/watchers that atomically rewrite files in the sync directory during uninstall
- Serialize all script instances with a lockfile
- Always read the quarantinePath from the error message before deleting quarantine dirs
- Run uninstall/rollback in a quiet window with no parallel npm operations
When it happens
Trigger: The path was swapped (replaced by another file) between openRegularFileNoFollow and the quarantine rename during rollbackLegacyCodexSync/uninstallLegacyCodexSync, AND the restore attempt (linkSync/unlinkSync/rmdirSync of the quarantine) itself failed — e.g. the original path reappeared, permissions changed, or the quarantine dir was disturbed concurrently.
Common situations: A concurrent installer or file watcher recreates the sync file during uninstall; another instance of the script is mid-operation on the same path; restore fails because the original path got recreated by the racing process.
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
- Invalid ECC repo root: unreadable package.json at
- Legacy sync path changed before removal
- Legacy sync path changed while opening
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/e85140edd5923bae.
Report an issue: GitHub.
Appendix: 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 8321021c54)