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

artifact_missing

artifact_missing

Error message

artifact_missing

What it means

Thrown as a plain Error with text "artifact_missing" when realpath() on the resolved artifact path fails with ENOENT — the file (or a directory component of the path) does not exist on disk. Callers are expected to match on the message text to detect missing artifacts.

Solutions

  1. Wait for or verify the step that produces the artifact completed before fetching
  2. Check existsSync/await stat on the file before calling the tool
  3. Catch the error and compare err.message === 'artifact_missing' to degrade gracefully

Example fix

// before
const res = await tool({ path: "artifacts/out.txt" });
// after
if (!existsSync(join(cwd, "artifacts/out.txt"))) throw new Error("artifact not produced yet");
const res = await tool({ path: "artifacts/out.txt" });
Defensive patterns

Strategy: type-guard

Validate before calling

import { existsSync } from 'node:fs';
if (!existsSync(path.join(cwd, relPath))) throw new Error('artifact not produced yet');

Type guard

async function artifactExists(cwd: string, rel: string): Promise<boolean> { try { await fs.stat(path.join(cwd, rel)); return true; } catch { return false; } }

Try / catch

try { await readArtifact(cwd, p); } catch (e) { if ((e as Error).message === 'artifact_missing') return null; throw e; }

Prevention

When it happens

Trigger: Requesting an artifact before the producing step has written it; typo in the filename; a symlink chain where an intermediate link is dangling (realpath fails with ENOENT).

Common situations: Race between agent finishing a build and the client fetching its output; artifacts cleaned up by a temp-dir sweeper; CI caching that excludes the artifacts directory.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


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

Appendix: source

Thrown at src/mcp/hermes-bridge.ts:530

function isInsideDirectory(parent: string, candidate: string): boolean {
  const rel = relative(parent, candidate);
  return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
}

async function resolveSafeArtifactPath(cwd: string, rel: string): Promise<string> {
  const cwdRealPath = await realpath(cwd);
  const full = resolve(cwd, rel);
  const relativeToCwd = relative(resolve(cwd), full);
  if (relativeToCwd.startsWith("..") || isAbsolute(relativeToCwd)) {
    throw new Error("artifact resolved outside working directory");
  }

  let artifactRealPath: string;
  try {
    artifactRealPath = await realpath(full);
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code === "ENOENT") throw new Error("artifact_missing");
    throw error;
  }

  if (!isInsideDirectory(cwdRealPath, artifactRealPath)) {
    throw new Error("artifact resolved outside working directory");
  }

  for (const prefix of SAFE_ARTIFACT_PREFIXES) {
    const rootRealPath = await realpath(resolve(cwd, prefix)).catch(() => null);
    if (rootRealPath && isInsideDirectory(rootRealPath, artifactRealPath)) return artifactRealPath;
  }

  throw new Error(`artifact path must be under ${SAFE_ARTIFACT_PREFIXES.join(", ")}`);
}

async function collectFiles(root: string, cwd: string, limit: number, out: Array<{ path: string; bytes: number }>): Promise<void> {
  if (out.length >= limit || !existsSync(root)) return;
  const cwdRealPath = await realpath(cwd);

View on GitHub (pinned to 3ad79a8a6f)