affaan-m/ECC · error · Error
Refusing to manage non-regular legacy sync path: ${filePath}
Error message
Refusing to manage non-regular legacy sync path: ${filePath} What it means
codex-legacy-sync opens the legacy sync state file with O_NOFOLLOW to refuse symlink traversal. In this branch the open failed with ENOENT, yet a follow-up lstat of the same path still resolves — and shows a symlink or a non-regular file. That combination means the final component is a symlink (typically dangling, pointing at a missing target) or a special file, and the module refuses to manage such a path rather than follow it.
Source
Thrown at scripts/lib/codex-legacy-sync.js:28
const BEGIN_MARKER = '<!-- BEGIN ECC -->';
const END_MARKER = '<!-- END ECC -->';
function getStatePath(codexHome) {
return path.join(codexHome, 'ecc', 'legacy-sync-state.json');
}
function openRegularFileNoFollow(filePath, writable = false) {
const noFollow = fs.constants.O_NOFOLLOW || 0;
const flags = (writable ? fs.constants.O_RDWR : fs.constants.O_RDONLY) | noFollow;
let descriptor;
try {
descriptor = fs.openSync(filePath, flags);
} catch (error) {
if (error.code === 'ENOENT') {
try {
const unresolved = fs.lstatSync(filePath);
if (unresolved.isSymbolicLink() || !unresolved.isFile()) {
throw new Error(`Refusing to manage non-regular legacy sync path: ${filePath}`);
}
} 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);View on GitHub (pinned to 06c5e118c4)
Solutions
- Inspect the path: `ls -la <statePath>` and `readlink <statePath>` to see the symlink target.
- Remove the symlink and let the sync recreate a real regular file at that path (or restore a backup of the original file).
- If the target lives on an unmounted volume (cloud sync), mount it or move the state file back to local disk.
Example fix
# before $ ls -la ~/.codex/ecc-legacy-sync.json lrwxrwxrwx ... -> ~/Dropbox/dotfiles/ecc-legacy-sync.json (missing) # after $ rm ~/.codex/ecc-legacy-sync.json $ ecc codex sync # recreates a real regular file
Defensive patterns
Strategy: validation
Validate before calling
import fs from 'node:fs';
function assertManageableStatePath(p) {
let st;
try { st = fs.lstatSync(p); }
catch (e) { if (e.code === 'ENOENT') return; throw e; }
if (st.isSymbolicLink()) throw new Error(`refusing: ${p} is a symlink`);
if (!st.isFile()) throw new Error(`refusing: ${p} is not a regular file`);
} Type guard
function isRegularFileNoSymlink(p) {
try { const st = fs.lstatSync(p); return st.isFile() && !st.isSymbolicLink(); } catch { return false; }
} Prevention
- Keep sync state files on local disk as real files; exclude them from dotfile symlinking.
- Before running sync, quickly audit the state dir: find <dir> -type l to catch dangling or valid symlinks.
When it happens
Trigger: The Codex legacy sync state file was replaced by a symlink whose target does not exist (dotfile manager pointing at an unmounted cloud-sync folder), or the path is a FIFO/device.
Common situations: Users symlinking dotfiles into Dropbox/iCloud-driven dirs that are not mounted; cleanup tools that leave dangling symlinks behind; misconfigured stow/dotbot setups touching the ECC/Codex state path.
Related errors
- Refusing to ${action}: destination parent is not a trusted d
- Refusing to ${action} through symlinked Claude skill path: '
- Refusing to access memory through symlink root: ${root}
- Refusing to access memory through symlink directory: ${direc
- Path traversal rejected: ${relPath}
AI-assisted analysis of affaan-m/ECC@06c5e118c4 (2026-08-18).
Data as JSON: /api/errors/5b5106894a403620.
Report an issue: GitHub.