affaan-m/ECC · error · Error
Legacy sync path changed while opening
Error message
Legacy sync path changed while opening: ${filePath} What it means
openRegularFileNoFollow opens a path with O_NOFOLLOW, then re-lstats the path to verify the file did not change during the open. If the second lstatSync fails with ENOENT, the file at the path was removed or renamed between the open and the verification check, so the library aborts rather than operating on a vanished or swapped target. This is part of the TOCTOU hardening that guards legacy codex sync files against symlink and race attacks.
Solutions
- Re-run the uninstall/rollback command once no other process is touching the repository
- Check that the target file exists (fs.lstatSync) before invoking rollback/uninstall and skip if already gone
- Ensure only one instance of the script runs at a time (use a lockfile or flock)
- Exclude the sync path from antivirus/cleanup watchers that may remove files mid-operation
Example fix
// before
await uninstallLegacyCodexSync(targetPath);
// after
import fs from 'node:fs';
let exists = true;
try { fs.lstatSync(targetPath); } catch (e) { if (e.code === 'ENOENT') exists = false; else throw e; }
if (exists) await uninstallLegacyCodexSync(targetPath); Defensive patterns
Strategy: try-catch
Validate before calling
import fs from 'node:fs';
let exists = false;
try { const s = fs.lstatSync(p); exists = s.isFile(); } catch (e) { if (e.code !== 'ENOENT') throw e; }
if (!exists) return; // nothing to uninstall/roll back Try / catch
try {
await uninstallLegacyCodexSync(p);
} catch (err) {
if (err.message.includes('Legacy sync path changed while opening')) {
// file vanished mid-operation; treat as already removed
} else throw err;
} Prevention
- Check file existence with lstat before calling rollback/uninstall
- Run only one instance of the script at a time (lockfile/flock)
- Exclude the sync path from antivirus or cleanup daemons
- Retry the operation once on this error; the path state is now stable
When it happens
Trigger: Another process unlinks or renames filePath in the window between fs.openSync and the fs.lstatSync(filePath, { bigint: true }) verification inside openRegularFileNoFollow; reached via opened(), quarantined(), rollbackLegacyCodexSync(), or uninstallLegacyCodexSync().
Common situations: A concurrent install/uninstall or npm operation removes the legacy codex sync file while this script runs; antivirus or cleanup tools quarantine the file mid-operation; two copies of the script racing on the same path.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 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 before removal; preserved…
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/a29839056da388aa.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/codex-legacy-sync.js:48
} catch (lstatError) {
if (lstatError.code === 'ENOENT') return null;
throw lstatError;
}
throw error;
}
if (error.code === 'ELOOP') {
throw new Error(`Refusing to manage non-regular legacy sync path: ${filePath}`);
}
throw error;
}
const descriptorStat = fs.fstatSync(descriptor, { bigint: true });
let finalPathStat;
try {
finalPathStat = fs.lstatSync(filePath, { bigint: true });
} catch (error) {
fs.closeSync(descriptor);
if (error.code === 'ENOENT') {
throw new Error(`Legacy sync path changed while opening: ${filePath}`);
}
throw error;
}
if (
!descriptorStat.isFile()
|| !finalPathStat.isFile()
|| finalPathStat.isSymbolicLink()
|| descriptorStat.dev !== finalPathStat.dev
|| descriptorStat.ino !== finalPathStat.ino
|| descriptorStat.nlink !== 1n
|| finalPathStat.nlink !== 1n
) {
fs.closeSync(descriptor);
throw new Error(`Refusing to manage non-regular legacy sync path: ${filePath}`);
}
return { descriptor, stat: fs.fstatSync(descriptor) };
}
View on GitHub (pinned to 8321021c54)