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

Unsafe Autopilot context directory: resolved path escapes re

Error message

Unsafe Autopilot context directory: resolved path escapes repository root

What it means

Thrown by ensureSafeAutopilotContextDir when the realpath of .omx/context resolves outside the repository root (relative path from root is '', starts with '..', or is absolute — the latter indicating a different filesystem root). Even after passing the symlink checks, the resolved real path must remain within the repo.

Source

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

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);
  for (let attempt = 0; attempt < 100; attempt += 1) {
    const suffix = attempt === 0 ? '' : `-${attempt + 1}`;
    const filename = `${slug}-${timestamp}${suffix}.md`;
    const relativePath = `.omx/context/${filename}`;
    const absolutePath = resolve(contextDir, filename);
    try {

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Run the hook from the real repository path (resolve symlinks in your cwd: cd $(realpath .)) and avoid symlinked checkouts
  2. Remove bind mounts/junctions on .omx or .omx/context so the directory physically lives under the repo root
  3. On Windows, ensure the repo and .omx are on the same drive root and not accessed via a subst drive letter
  4. If using containers, mount the whole repo rather than just the .omx subdirectory

Example fix

# before
cd ~/link-to-repo && run-hook  # sourceCwd is a symlink; realpath escapes

# after
cd ~/work/myrepo && run-hook  # real path; context resolves inside root
Defensive patterns

Strategy: validation

Validate before calling

import { realpath, relative } from 'node:fs/promises';
import { isAbsolute } from 'node:path';

async function contextWithinRoot(cwd: string): Promise<boolean> {
  const root = await realpath(cwd);
  const ctx = await realpath(join(cwd, '.omx', 'context')).catch(() => null);
  if (!ctx) return true;
  const rel = relative(root, ctx);
  return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel);
}

Try / catch

try { await ensureAutopilotContextSnapshot(cwd, iso, text); } catch (err) {
  if ((err as Error).message.includes('escapes repository root')) {
    const realCwd = await realpath(cwd);
    return ensureAutopilotContextSnapshot(realCwd, iso, text);
  }
  throw err;
}

Prevention

When it happens

Trigger: .omx or .omx/context being a bind mount (Linux), a mount point, or residing in a repo whose realpath differs from sourceCwd (e.g. sourceCwd itself is a symlink to the actual checkout, making relative(rootRealPath, contextRealPath) escape); context dir on a different drive/root on Windows yielding an absolute relative() result.

Common situations: Checking out the repo through a symlinked path (ln -s ~/work/myrepo ~/link && cd ~/link); Docker bind mounts mapping .omx to a host path; Windows subst/junction drives; CI systems that materialize workspaces via links so realpath lands outside the nominal root.

Related errors


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