{"record":{"id":"79e23e4fb073ff04","repo":"affaan-m/ECC","slug":"refusing-to-manage-legacy-sync-path-through-symlin","errorCode":null,"errorMessage":"Refusing to manage legacy sync path through symlinked ancestor: ${filePath}","messagePattern":"Refusing to manage legacy sync path through symlinked ancestor: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/lib/codex-legacy-sync.js","lineNumber":282,"sourceCode":"\n  for (const [key, filePath] of [['config', configPath], ['agents', agentsPath]]) {\n    if (priorState) break;\n    const snapshot = readRegularFileNoFollow(filePath, 'utf8');\n    state.before[key] = snapshot ? snapshot.content : null;\n  }\n  atomicWriteJson(statePath, state);\n  return statePath;\n}\n\nfunction recordLegacySyncPath(options) {\n  const state = readState(options.statePath);\n  const filePath = path.resolve(options.filePath);\n  const trustedRoot = getTrustedRoot(state, filePath);\n  if (!trustedRoot) {\n    throw new Error(`Refusing to record a legacy sync path outside trusted roots: ${filePath}`);\n  }\n  if (hasUnsafeManagedAncestor(filePath, trustedRoot)) {\n    throw new Error(`Refusing to manage legacy sync path through symlinked ancestor: ${filePath}`);\n  }\n  if (!state.paths.some(entry => entry.path === filePath)) {\n    const snapshot = snapshotLegacyPath(filePath);\n    state.paths.push(snapshot);\n    state.rollbackPaths = [...(state.rollbackPaths || []), { ...snapshot }];\n    atomicWriteJson(options.statePath, state);\n  }\n}\n\nfunction rollbackLegacyCodexSync(options) {\n  const state = readState(options.statePath);\n  const restoredPaths = [];\n  const retainedPaths = [];\n\n  const rollbackPaths = Array.isArray(state.rollbackPaths) ? state.rollbackPaths : state.paths;\n  for (const entry of [...rollbackPaths].reverse()) {\n    const filePath = path.resolve(entry.path);\n    const trustedRoot = getTrustedRoot(state, filePath);","sourceCodeStart":264,"sourceCodeEnd":300,"githubUrl":"https://github.com/affaan-m/ECC/blob/06c5e118c4d3e6c3b7f9445f973a2194c82de193/scripts/lib/codex-legacy-sync.js#L264-L300","documentation":"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.","triggerScenarios":"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.","commonSituations":"Dotfiles managers symlinking config directories; container images linking CODEX_HOME subdirectories to volumes; stow-managed home directories.","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"],"exampleFix":"# before: ~/.codex/hooks is a symlink -> Refusing to manage legacy sync path through symlinked ancestor\nrm ~/.codex/hooks && mkdir ~/.codex/hooks && cp ~/dotfiles/hooks/* ~/.codex/hooks/\n# after: re-run the sync","handlingStrategy":"validation","validationCode":"const fs = require('fs'), path = require('path');\nfunction hasSymlinkedAncestor(filePath, root) {\n  const segs = path.relative(root, path.resolve(filePath)).split(path.sep).slice(0, -1);\n  let cur = path.resolve(root);\n  for (const seg of segs) {\n    cur = path.join(cur, seg);\n    if (fs.lstatSync(cur).isSymbolicLink()) return true;\n  }\n  return false;\n}\nif (hasSymlinkedAncestor(filePath, codexHome)) fixLayoutBeforeRecording(filePath);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["codex","symlink","path-validation","security"],"backgroundTag":"symlink-path-resolution","analyzedSha":"06c5e118c4d3e6c3b7f9445f973a2194c82de193","analyzedAt":"2026-08-18T11:27:13.915Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}