{"record":{"id":"ed6a2001c3385e77","repo":"Yeachan-Heo/oh-my-codex","slug":"unsafe-autopilot-context-directory-omx-context-i","errorCode":null,"errorMessage":"Unsafe Autopilot context directory: .omx/context is a symbolic link","messagePattern":"Unsafe Autopilot context directory: \\.omx/context is a symbolic link","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/hooks/keyword-detector.ts","lineNumber":371,"sourceCode":"const AUTOPILOT_CONTEXT_RECOVERY_REASON_MESSAGES: Record<AutopilotContextRecoveryReason, string> = {\n  'missing-or-unsafe-legacy-context-snapshot': 'no safe legacy Autopilot context snapshot path was available during continuation.',\n  'missing-autopilot-mode-state': 'active Autopilot skill state existed but no matching Autopilot mode state was available during continuation.',\n  'malformed-autopilot-mode-state': 'active Autopilot mode state could not be parsed during continuation.',\n  'nonpreservable-autopilot-mode-state-missing-current-phase': 'active Autopilot mode state was missing current_phase during continuation.',\n};\n\nasync function ensureSafeAutopilotContextDir(sourceCwd: string): Promise<string> {\n  const rootRealPath = await realpath(sourceCwd);\n  const omxDir = join(sourceCwd, '.omx');\n  await mkdir(omxDir, { recursive: true });\n  if ((await lstat(omxDir)).isSymbolicLink()) {\n    throw new Error('Unsafe Autopilot context directory: .omx is a symbolic link');\n  }\n\n  const contextDir = join(omxDir, 'context');\n  await mkdir(contextDir, { recursive: true });\n  if ((await lstat(contextDir)).isSymbolicLink()) {\n    throw new Error('Unsafe Autopilot context directory: .omx/context is a symbolic link');\n  }\n\n  const contextRealPath = await realpath(contextDir);\n  const relativeToRoot = relative(rootRealPath, contextRealPath);\n  if (relativeToRoot === '' || relativeToRoot.startsWith('..') || isAbsolute(relativeToRoot)) {\n    throw new Error('Unsafe Autopilot context directory: resolved path escapes repository root');\n  }\n  return contextDir;\n}\n\nasync function writeUniqueAutopilotContextSnapshot(\n  sourceCwd: string,\n  slug: string,\n  nowIso: string,\n  body: string,\n): Promise<string> {\n  const contextDir = await ensureSafeAutopilotContextDir(sourceCwd);\n  const timestamp = utcCompactTimestamp(nowIso);","sourceCodeStart":353,"sourceCodeEnd":389,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/hooks/keyword-detector.ts#L353-L389","documentation":"Thrown by ensureSafeAutopilotContextDir when .omx/context is a symbolic link. Same rationale as the .omx check: snapshot writes must stay inside the real repository tree, so a symlinked context directory is rejected via lstat before any write happens.","triggerScenarios":"Running the Autopilot hook when .omx exists as a real directory but .omx/context is a symlink (e.g. points to a temp dir or another project's context).","commonSituations":"Manually moving context snapshots elsewhere and linking back; cleanup scripts that replaced the dir with a link; tools that 'deduplicate' directories by symlinking; partially-migrated repos where only context was linked.","solutions":["Remove the symlink and recreate a real directory: rm .omx/context && mkdir .omx/context","Migrate old snapshots by copying (cp -r) rather than linking, then delete the link","Check for tooling in your workflow that creates symlinks under .omx and disable it for this path"],"exampleFix":"# before\n.omx/context -> /tmp/autopilot-context  (symlink)\n\n# after\nrm .omx/context\nmkdir .omx/context","handlingStrategy":"validation","validationCode":"import { lstat } from 'node:fs/promises';\n\nasync function contextIsSafe(cwd: string): Promise<boolean> {\n  try { return !(await lstat(join(cwd, '.omx', 'context'))).isSymbolicLink(); }\n  catch { return true; }\n}","typeGuard":null,"tryCatchPattern":"try { await ensureAutopilotContextSnapshot(cwd, iso, text); } catch (err) {\n  if ((err as Error).message.includes('.omx/context is a symbolic link')) {\n    await rm(join(cwd, '.omx', 'context'), { force: true });\n    await mkdir(join(cwd, '.omx', 'context'), { recursive: true });\n    return ensureAutopilotContextSnapshot(cwd, iso, text);\n  }\n  throw err;\n}","preventionTips":["Keep .omx/context as a plain directory; move old snapshots with cp, not ln -s","Audit cleanup/dedup tooling so it never replaces context with a link","Include a symlink check in repo preflight scripts"],"tags":["security","symlink","filesystem","path-safety"],"backgroundTag":"symlink-security-check-failed","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}