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

  1. Inspect the path: `ls -la <statePath>` and `readlink <statePath>` to see the symlink target.
  2. Remove the symlink and let the sync recreate a real regular file at that path (or restore a backup of the original file).
  3. 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

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


AI-assisted analysis of affaan-m/ECC@06c5e118c4 (2026-08-18). Data as JSON: /api/errors/5b5106894a403620. Report an issue: GitHub.