affaan-m/ECC · warning · Error
Legacy sync path changed before removal: ${filePath}
Error message
Legacy sync path changed before removal: ${filePath} What it means
The same quarantine identity check as error 97, but the better outcome: dev/ino of the opened file and its quarantined copy differ (the path was swapped after open), and the restore succeeded — the original content was linked back to the path, the quarantine removed, and the operation aborted cleanly. The throw exists so callers know the removal did NOT happen and the file that raced in was discarded safely.
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 06c5e118c4)
Solutions
- Simply re-run the command after the concurrent operation finishes — state is consistent because the original was restored.
- Serialize access: run sync from one shell/job at a time, or add a lock around state-file management.
- If it recurs, identify the second writer with lsof/fswatch on the state directory.
Example fix
# before $ ecc codex sync & ecc codex sync & # racing removals # Error: Legacy sync path changed before removal # after $ wait; ecc codex sync # single serialized run
Defensive patterns
Strategy: retry
Try / catch
try { removeLegacyState(statePath); } catch (e) {
if (/changed before removal/.test(e.message)) {
// original was restored automatically; retry once after the concurrent writer exits
return removeLegacyState(statePath);
}
throw e;
} Prevention
- One writer at a time: guard sync/remove operations with a lock so the swap race cannot happen.
- Treat this error as a loud signal that two processes are managing the same state file.
When it happens
Trigger: A concurrent process renames/replaces the state file between the open() and the unlink() phases of removeOpenedRegularFile, in a directory where the restore link can succeed.
Common situations: Two `ecc codex sync` runs (or a sync plus a state-rewriting tool) operating on the same legacy state path at the same moment.
Related errors
- Legacy sync path changed while opening: ${filePath}
- Refusing to create non-regular legacy sync path: ${filePath}
- Legacy sync path changed before removal; preserved replaceme
- Refusing to manage non-regular legacy sync path: ${filePath}
- Legacy Codex sync state not found at ${statePath}
AI-assisted analysis of affaan-m/ECC@06c5e118c4 (2026-08-18).
Data as JSON: /api/errors/8921a5bb281ea6b0.
Report an issue: GitHub.