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

  1. Re-run the uninstall/rollback command once no other process is touching the repository
  2. Check that the target file exists (fs.lstatSync) before invoking rollback/uninstall and skip if already gone
  3. Ensure only one instance of the script runs at a time (use a lockfile or flock)
  4. 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

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


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)