Yeachan-Heo/oh-my-codex · error · Error
Unsafe Autopilot context directory: .omx is a symbolic link
Error message
Unsafe Autopilot context directory: .omx is a symbolic link
What it means
Thrown by ensureSafeAutopilotContextDir when the .omx directory in the working repo is a symbolic link. Because context snapshots are written inside .omx/context, a symlinked .omx could redirect writes outside the repository, so the hook refuses to proceed.
Source
Thrown at src/hooks/keyword-detector.ts:365
path: string;
kind: AutopilotContextSnapshotKind;
original_task_status?: 'activation-prompt' | 'legacy-unverified' | 'unavailable';
recovery?: Record<string, unknown>;
}
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,View on GitHub (pinned to 3ad79a8a6f)
Solutions
- Remove the symlink and use a real directory: rm .omx && mkdir .omx
- If you need shared/centralized state, configure the hook's state directory option if available instead of symlinking
- Audit your setup scripts/dotfiles for anything creating the .omx symlink
Example fix
# before .omx -> ~/.shared-omx (symlink) # after rm .omx mkdir .omx
Defensive patterns
Strategy: validation
Validate before calling
import { lstat } from 'node:fs/promises';
async function omxIsSafe(cwd: string): Promise<boolean> {
try { return !(await lstat(join(cwd, '.omx'))).isSymbolicLink(); }
catch { return true; } // doesn't exist yet; hook will create it
} Try / catch
try { await ensureAutopilotContextSnapshot(cwd, iso, text); } catch (err) {
if ((err as Error).message.includes('.omx is a symbolic link')) {
await rm(join(cwd, '.omx'), { force: true });
await mkdir(join(cwd, '.omx'), { recursive: true });
return ensureAutopilotContextSnapshot(cwd, iso, text);
}
throw err;
} Prevention
- Never symlink .omx to share state across checkouts
- Add .omx handling to repo bootstrap scripts to guarantee a real directory
- Document that overlay/state sharing must go through supported config, not symlinks
When it happens
Trigger: Running the Autopilot keyword-detector hook in a repo where .omx is a symlink to another directory (e.g. to share state or save disk via ln -s ~/.omx-shared .omx). The check uses lstat, so the link itself is detected even if its target is inside the repo.
Common situations: Developers symlinking .omx to a shared/global directory across checkouts; dotfile managers that symlink directories; monorepo setups trying to centralize .omx state; restore from a backup tool that converts directories to links.
Related errors
- Unsafe Autopilot context directory: .omx/context is a symbol
- Refusing cancellation through non-regular run state target:
- Refusing cancellation through non-regular state target ${ref
- Refusing to use unsafe backup ancestor ${currentPath}.
- run directory escapes the authorized runs root
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/45caa498a820ac42.
Report an issue: GitHub.