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

state directory escapes the authorized run directory

Error message

state directory escapes the authorized run directory

What it means

The .omx/state directory inside the canonicalized run dir resolves (after realpath) outside that run dir, typically because a path component is a symlink; the code refuses to operate on it.

Source

Thrown at src/cli/index.ts:8151

  for (const file of files) {
    if (!file.endsWith(".json")) continue;
    const record = readMadmaxDetachedActiveRecord(join(activeDir, file));
    if (!record || file !== `${record.context_key}.json`) continue;
    if (!record.session_id || normalizeSessionId(record.session_id) !== record.session_id) continue;
    if (!hasMatchingMadmaxDetachedRuntimeBinding(record)) continue;
    if (
      canonicalizePathForRunDirMatch(record.source_cwd) !== canonicalCwd
      && (!record.worktree_cwd || canonicalizePathForRunDirMatch(record.worktree_cwd) !== canonicalCwd)
    ) continue;

    try {
      const canonicalRunDir = realpathSync(resolve(record.run_dir));
      if (!isCanonicalPathWithin(canonicalRunsRoot, canonicalRunDir)) {
        throw new Error("run directory escapes the authorized runs root");
      }
      const stateDir = realpathSync(join(canonicalRunDir, ".omx", "state"));
      if (!isCanonicalPathWithin(canonicalRunDir, stateDir)) {
        throw new Error("state directory escapes the authorized run directory");
      }
      const session = JSON.parse(await readFile(join(stateDir, "session.json"), "utf-8")) as Record<string, unknown>;
      if (session.session_id !== record.session_id) throw new Error("run session pointer changed");
      const sessionDir = realpathSync(join(stateDir, "sessions", record.session_id));
      if (!isCanonicalPathWithin(stateDir, sessionDir)) {
        throw new Error("session directory escapes the authorized state directory");
      }
      candidates.push({ sessionDir, sessionId: record.session_id, record });
    } catch (err) {
      throw new Error(`Refusing cancellation because detached run authority is invalid: ${record.run_dir}.`, { cause: err });
    }
  }

  if (candidates.length > 1) throw new Error("Refusing cancellation because multiple detached run authorities match.");
  if (candidates.length === 0) return null;
  const [{ sessionDir, sessionId, record }] = candidates;
  const refs: ModeStateFileRef[] = [];
  const stateFiles = await readdir(sessionDir).catch(() => [] as string[]);

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Remove the symlink at <run_dir>/.omx/state (and .omx if needed) so the state dir is a real directory inside the run dir
  2. Restore state from the proper location or re-launch the session to recreate state
  3. Avoid symlinking .omx internals; keep run dirs self-contained
Defensive patterns

Strategy: validation

Validate before calling

const state = realpathSync(join(runDir, '.omx', 'state'));
if (!state.startsWith(realpathSync(runDir) + sep)) throw new Error('.omx/state must live inside run dir');

Prevention

When it happens

Trigger: realpathSync(join(canonicalRunDir, '.omx', 'state')) lands outside canonicalRunDir — e.g. .omx or state is a symlink to another location.

Common situations: Users symlinking .omx to shared storage or another project; malicious or accidental tampering with the state dir.

Related errors


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