Yeachan-Heo/oh-my-codex · error · Error

Unsafe Autopilot context directory: .omx/context is a symbol

Error message

Unsafe Autopilot context directory: .omx/context is a symbolic link

What it means

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.

Source

Thrown at src/hooks/keyword-detector.ts:371

const AUTOPILOT_CONTEXT_RECOVERY_REASON_MESSAGES: Record<AutopilotContextRecoveryReason, string> = {
  'missing-or-unsafe-legacy-context-snapshot': 'no safe legacy Autopilot context snapshot path was available during continuation.',
  'missing-autopilot-mode-state': 'active Autopilot skill state existed but no matching Autopilot mode state was available during continuation.',
  'malformed-autopilot-mode-state': 'active Autopilot mode state could not be parsed during continuation.',
  'nonpreservable-autopilot-mode-state-missing-current-phase': 'active Autopilot mode state was missing current_phase during continuation.',
};

async function ensureSafeAutopilotContextDir(sourceCwd: string): Promise<string> {
  const rootRealPath = await realpath(sourceCwd);
  const omxDir = join(sourceCwd, '.omx');
  await mkdir(omxDir, { recursive: true });
  if ((await lstat(omxDir)).isSymbolicLink()) {
    throw new Error('Unsafe Autopilot context directory: .omx is a symbolic link');
  }

  const contextDir = join(omxDir, 'context');
  await mkdir(contextDir, { recursive: true });
  if ((await lstat(contextDir)).isSymbolicLink()) {
    throw new Error('Unsafe Autopilot context directory: .omx/context is a symbolic link');
  }

  const contextRealPath = await realpath(contextDir);
  const relativeToRoot = relative(rootRealPath, contextRealPath);
  if (relativeToRoot === '' || relativeToRoot.startsWith('..') || isAbsolute(relativeToRoot)) {
    throw new Error('Unsafe Autopilot context directory: resolved path escapes repository root');
  }
  return contextDir;
}

async function writeUniqueAutopilotContextSnapshot(
  sourceCwd: string,
  slug: string,
  nowIso: string,
  body: string,
): Promise<string> {
  const contextDir = await ensureSafeAutopilotContextDir(sourceCwd);
  const timestamp = utcCompactTimestamp(nowIso);

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Remove the symlink and recreate a real directory: rm .omx/context && mkdir .omx/context
  2. Migrate old snapshots by copying (cp -r) rather than linking, then delete the link
  3. Check for tooling in your workflow that creates symlinks under .omx and disable it for this path

Example fix

# before
.omx/context -> /tmp/autopilot-context  (symlink)

# after
rm .omx/context
mkdir .omx/context
Defensive patterns

Strategy: validation

Validate before calling

import { lstat } from 'node:fs/promises';

async function contextIsSafe(cwd: string): Promise<boolean> {
  try { return !(await lstat(join(cwd, '.omx', 'context'))).isSymbolicLink(); }
  catch { return true; }
}

Try / catch

try { await ensureAutopilotContextSnapshot(cwd, iso, text); } catch (err) {
  if ((err as Error).message.includes('.omx/context is a symbolic link')) {
    await rm(join(cwd, '.omx', 'context'), { force: true });
    await mkdir(join(cwd, '.omx', 'context'), { recursive: true });
    return ensureAutopilotContextSnapshot(cwd, iso, text);
  }
  throw err;
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27). Data as JSON: /api/errors/ed6a2001c3385e77. Report an issue: GitHub.