affaan-m/ECC · error · Error
Refusing to manage legacy sync path through symlinked ancest
Error message
Refusing to manage legacy sync path through symlinked ancestor: ${filePath} What it means
recordLegacySyncPath() also runs hasUnsafeManagedAncestor(): it walks every directory segment between the trusted root and the target file and refuses when any segment is a symlink. Managing a path through a symlinked directory could redirect writes outside the tree the operator granted, so the recording is rejected.
Source
Thrown at scripts/lib/codex-legacy-sync.js:282
for (const [key, filePath] of [['config', configPath], ['agents', agentsPath]]) {
if (priorState) break;
const snapshot = readRegularFileNoFollow(filePath, 'utf8');
state.before[key] = snapshot ? snapshot.content : null;
}
atomicWriteJson(statePath, state);
return statePath;
}
function recordLegacySyncPath(options) {
const state = readState(options.statePath);
const filePath = path.resolve(options.filePath);
const trustedRoot = getTrustedRoot(state, filePath);
if (!trustedRoot) {
throw new Error(`Refusing to record a legacy sync path outside trusted roots: ${filePath}`);
}
if (hasUnsafeManagedAncestor(filePath, trustedRoot)) {
throw new Error(`Refusing to manage legacy sync path through symlinked ancestor: ${filePath}`);
}
if (!state.paths.some(entry => entry.path === filePath)) {
const snapshot = snapshotLegacyPath(filePath);
state.paths.push(snapshot);
state.rollbackPaths = [...(state.rollbackPaths || []), { ...snapshot }];
atomicWriteJson(options.statePath, state);
}
}
function rollbackLegacyCodexSync(options) {
const state = readState(options.statePath);
const restoredPaths = [];
const retainedPaths = [];
const rollbackPaths = Array.isArray(state.rollbackPaths) ? state.rollbackPaths : state.paths;
for (const entry of [...rollbackPaths].reverse()) {
const filePath = path.resolve(entry.path);
const trustedRoot = getTrustedRoot(state, filePath);View on GitHub (pinned to 06c5e118c4)
Solutions
- Replace the symlinked directory with a real directory (move the target's contents in) and re-run
- Point codexHome directly at the symlink's real target so no symlink sits inside the managed tree
- Record a path that does not traverse the symlinked ancestor
Example fix
# before: ~/.codex/hooks is a symlink -> Refusing to manage legacy sync path through symlinked ancestor rm ~/.codex/hooks && mkdir ~/.codex/hooks && cp ~/dotfiles/hooks/* ~/.codex/hooks/ # after: re-run the sync
Defensive patterns
Strategy: validation
Validate before calling
const fs = require('fs'), path = require('path');
function hasSymlinkedAncestor(filePath, root) {
const segs = path.relative(root, path.resolve(filePath)).split(path.sep).slice(0, -1);
let cur = path.resolve(root);
for (const seg of segs) {
cur = path.join(cur, seg);
if (fs.lstatSync(cur).isSymbolicLink()) return true;
}
return false;
}
if (hasSymlinkedAncestor(filePath, codexHome)) fixLayoutBeforeRecording(filePath); Prevention
- Use real directories, not symlinks, inside CODEX_HOME
- Point codexHome at the real target of any symlink instead of the link
- Keep dotfile stow targets outside the managed tree
When it happens
Trigger: Recording a file whose parent chain inside codexHome contains a symlink — e.g. ~/.codex is real but ~/.codex/hooks -> ~/dotfiles/hooks, and the caller records ~/.codex/hooks/hook.json.
Common situations: Dotfiles managers symlinking config directories; container images linking CODEX_HOME subdirectories to volumes; stow-managed home directories.
Related errors
- Refusing to reuse unsafe legacy Codex ownership path: ${file
- 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/79e23e4fb073ff04.
Report an issue: GitHub.