{"record":{"id":"99ccc97c1351f50a","repo":"Yeachan-Heo/oh-my-codex","slug":"artifact-missing","errorCode":"artifact_missing","errorMessage":"artifact_missing","messagePattern":"artifact_missing","errorType":"error_code","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/mcp/hermes-bridge.ts","lineNumber":530,"sourceCode":"\nfunction isInsideDirectory(parent: string, candidate: string): boolean {\n  const rel = relative(parent, candidate);\n  return rel === \"\" || (!rel.startsWith(\"..\") && !isAbsolute(rel));\n}\n\nasync function resolveSafeArtifactPath(cwd: string, rel: string): Promise<string> {\n  const cwdRealPath = await realpath(cwd);\n  const full = resolve(cwd, rel);\n  const relativeToCwd = relative(resolve(cwd), full);\n  if (relativeToCwd.startsWith(\"..\") || isAbsolute(relativeToCwd)) {\n    throw new Error(\"artifact resolved outside working directory\");\n  }\n\n  let artifactRealPath: string;\n  try {\n    artifactRealPath = await realpath(full);\n  } catch (error) {\n    if ((error as NodeJS.ErrnoException).code === \"ENOENT\") throw new Error(\"artifact_missing\");\n    throw error;\n  }\n\n  if (!isInsideDirectory(cwdRealPath, artifactRealPath)) {\n    throw new Error(\"artifact resolved outside working directory\");\n  }\n\n  for (const prefix of SAFE_ARTIFACT_PREFIXES) {\n    const rootRealPath = await realpath(resolve(cwd, prefix)).catch(() => null);\n    if (rootRealPath && isInsideDirectory(rootRealPath, artifactRealPath)) return artifactRealPath;\n  }\n\n  throw new Error(`artifact path must be under ${SAFE_ARTIFACT_PREFIXES.join(\", \")}`);\n}\n\nasync function collectFiles(root: string, cwd: string, limit: number, out: Array<{ path: string; bytes: number }>): Promise<void> {\n  if (out.length >= limit || !existsSync(root)) return;\n  const cwdRealPath = await realpath(cwd);","sourceCodeStart":512,"sourceCodeEnd":548,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/mcp/hermes-bridge.ts#L512-L548","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Wait for or verify the step that produces the artifact completed before fetching","Check existsSync/await stat on the file before calling the tool","Catch the error and compare err.message === 'artifact_missing' to degrade gracefully"],"exampleFix":"// before\nconst res = await tool({ path: \"artifacts/out.txt\" });\n// after\nif (!existsSync(join(cwd, \"artifacts/out.txt\"))) throw new Error(\"artifact not produced yet\");\nconst res = await tool({ path: \"artifacts/out.txt\" });","handlingStrategy":"type-guard","validationCode":"import { existsSync } from 'node:fs';\nif (!existsSync(path.join(cwd, relPath))) throw new Error('artifact not produced yet');","typeGuard":"async function artifactExists(cwd: string, rel: string): Promise<boolean> { try { await fs.stat(path.join(cwd, rel)); return true; } catch { return false; } }","tryCatchPattern":"try { await readArtifact(cwd, p); } catch (e) { if ((e as Error).message === 'artifact_missing') return null; throw e; }","preventionTips":["Poll for file existence before fetching artifacts","Treat artifact_missing as a retryable condition until a timeout"],"tags":["file-not-found","artifact","mcp"],"backgroundTag":"file-not-found","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}