Yeachan-Heo/oh-my-codex · error · Error
session history resolved outside working directory
Error message
session history resolved outside working directory
What it means
Thrown when the session-history file (.omx/logs/session-history.jsonl) resolves, after realpath, to a location outside the real working directory. Like the artifact checks, this prevents reading history through symlinked paths that escape the sandbox.
Source
Thrown at src/mcp/hermes-bridge.ts:624
if (message.includes("artifact path") || message.includes("outside working directory")) {
return failure("artifact_outside_safe_roots", message);
}
return failure("invalid_input", message);
}
}
export async function hermesReadTail(
args: Record<string, unknown>,
): Promise<HermesBridgeResult<{ tail: string[]; path: string }>> {
try {
const cwd = resolveWorkingDirectoryForState(normalizeString(args.workingDirectory, "workingDirectory"));
const lines = normalizePositiveInteger(args.lines, DEFAULT_TAIL_LINES, MAX_TAIL_LINES);
const path = join(cwd, ".omx", "logs", "session-history.jsonl");
if (!existsSync(path)) return jsonResult({ tail: [], path });
const cwdRealPath = await realpath(cwd);
const pathRealPath = await realpath(path);
if (!isInsideDirectory(cwdRealPath, pathRealPath)) {
throw new Error("session history resolved outside working directory");
}
const info = await stat(path);
const readBytes = Math.min(info.size, MAX_TAIL_READ_BYTES);
const start = Math.max(0, info.size - readBytes);
const handle = await open(pathRealPath, "r");
try {
const buffer = Buffer.alloc(readBytes);
const { bytesRead } = await handle.read(buffer, 0, readBytes, start);
const prefix = start > 0 ? "\n" : "";
const content = `${prefix}${buffer.subarray(0, bytesRead).toString("utf-8")}`;
return jsonResult({ tail: content.split(/\r?\n/).filter(Boolean).slice(-lines), path });
} finally {
await handle.close();
}
} catch (error) {
return failure("invalid_input", error instanceof Error ? error.message : String(error));
}
}View on GitHub (pinned to 3ad79a8a6f)
Solutions
- Use the real, non-symlinked path to the working directory (fs.realpathSync it before passing it as workingDirectory)
- Remove symlinks for .omx/logs so history lives inside the checkout
- Recreate .omx/logs as a real directory inside the project
Example fix
// before
const cwd = "/tmp/proj-link";
// after
const cwd = realpathSync("/tmp/proj-link"); Defensive patterns
Strategy: validation
Validate before calling
const realCwd = realpathSync(cwd);
const realHist = realpathSync(path.join(cwd, '.omx', 'logs', 'session-history.jsonl'));
if (!realHist.startsWith(realCwd + path.sep)) throw new Error('history outside cwd'); Try / catch
try { await tailSessionHistory(cwd); } catch (e) { if ((e as Error).message.includes('outside working directory')) { /* rerun with realpathSync(cwd) */ } throw e; } Prevention
- Pass realpathSync(cwd) as workingDirectory, especially on macOS /tmp
- Never symlink .omx or .omx/logs outside the checkout
When it happens
Trigger: cwd contains a symlinked .omx or .omx/logs directory pointing elsewhere; cwd itself is a symlink so realpath(cwd) differs from the path used to join .omx/logs/...
Common situations: /tmp symlinked to /private/tmp on macOS; shared .omx directory symlinked between checkouts; running inside symlinked container volumes.
Related errors
- run directory escapes the authorized runs root
- state directory escapes the authorized run directory
- session directory escapes the authorized state directory
- Refusing cancellation through non-regular run state target:
- Refusing cancellation through non-regular state target ${ref
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/e0e5096a6c087eaf.
Report an issue: GitHub.