affaan-m/ECC · error · Error
Refusing to reuse unsafe legacy Codex ownership path: ${file
Error message
Refusing to reuse unsafe legacy Codex ownership path: ${filePath} What it means
During reinstall, assertInstalledStateUnmodified() replays every recorded path. For each entry it resolves a trusted root (codexHome, recorded trustedRoots, or the installedHooksPath) and refuses the entry when the file no longer sits under any trusted root, or when a directory between the root and the file is a symlink (hasUnsafeManagedAncestor walks each segment). This is a deliberate guard: recorded ownership must never be reused through a path that escaped the managed tree.
Source
Thrown at scripts/lib/codex-legacy-sync.js:208
function snapshotLegacyPath(filePath) {
const snapshot = readRegularFileNoFollow(filePath);
const previousType = snapshot ? 'file' : 'missing';
return {
path: filePath,
installedSha256: null,
previousType,
previousContentBase64: snapshot ? snapshot.content.toString('base64') : null,
previousMode: snapshot ? snapshot.mode : null,
};
}
function assertInstalledStateUnmodified(state) {
for (const entry of state.paths) {
const filePath = path.resolve(entry.path);
const trustedRoot = getTrustedRoot(state, filePath);
if (!trustedRoot || hasUnsafeManagedAncestor(filePath, trustedRoot)) {
throw new Error(`Refusing to reuse unsafe legacy Codex ownership path: ${filePath}`);
}
const snapshot = readRegularFileNoFollow(filePath);
if (!entry.installedSha256) {
if (snapshot) throw new Error(`Refusing to replace modified legacy Codex artifact: ${filePath}`);
continue;
}
const digest = snapshot
? crypto.createHash('sha256').update(snapshot.content).digest('hex')
: null;
if (digest !== entry.installedSha256) {
throw new Error(`Refusing to replace modified legacy Codex artifact: ${filePath}`);
}
}
}
function beginLegacySyncState(options) {
const codexHome = path.resolve(options.codexHome);
const statePath = getStatePath(codexHome);View on GitHub (pinned to 06c5e118c4)
Solutions
- Open the state file and list each entry.path; identify which no longer live under the current codexHome / trustedRoots / hooks path
- Restore the original layout (same absolute codexHome, no symlinked ancestors) and re-run
- If the entries are stale, back up and delete the state file so the next sync rebuilds ownership from scratch
- Replace any symlinked ancestor directory with a real directory before re-running
Example fix
// before: ~/.codex/hooks is a symlink -> Refusing to reuse unsafe legacy Codex ownership path
fs.rmSync(path.join(codexHome, 'hooks')); // removes the symlink only
fs.mkdirSync(path.join(codexHome, 'hooks'), { recursive: true });
// after: re-run the legacy Codex sync Defensive patterns
Strategy: validation
Validate before calling
const path = require('path'), fs = require('fs');
function entriesAreWithinRoots(entries, roots) {
const resolved = roots.map(r => path.resolve(r));
return entries.every(e => {
const p = path.resolve(e.path);
return resolved.some(r => p === r || p.startsWith(r + path.sep));
});
}
function hasSymlinkedAncestor(filePath, root) {
const segs = path.relative(root, filePath).split(path.sep).slice(0, -1);
let cur = root;
for (const seg of segs) {
cur = path.join(cur, seg);
if (fs.lstatSync(cur).isSymbolicLink()) return true;
}
return false;
} Try / catch
try { beginLegacySyncState({ codexHome }); }
catch (err) {
if (/unsafe legacy Codex ownership path/.test(err.message)) {
// print the offending entry.path and the trusted roots so the operator fixes the layout
} else throw err;
} Prevention
- Keep CODEX_HOME at a stable absolute path
- Avoid symlinking directories inside the Codex home
- Do not copy sync-state files between machines
When it happens
Trigger: Re-running the legacy Codex sync when a state entry references an absolute path outside the current trusted roots (CODEX_HOME moved/renamed after install, state file copied from another machine), or when an ancestor directory inside the trusted root was replaced by a symlink.
Common situations: Renaming or moving ~/.codex after the first install; dotfile managers replacing config subdirectories with symlinks; state created under a different user or container path.
Related errors
- Refusing to manage legacy sync path through symlinked ancest
- Invalid ${flag}: expected a single cache path segment
- Refusing to manage non-regular legacy sync path: ${filePath}
- Refusing to record a legacy sync path outside trusted roots:
- Refusing to ${action}: destination parent is not a trusted d
AI-assisted analysis of affaan-m/ECC@06c5e118c4 (2026-08-18).
Data as JSON: /api/errors/ab549c444da0718c.
Report an issue: GitHub.