Yeachan-Heo/oh-my-codex · error · MissionCommandError
No mission summary found at ${summaryPath}.
Error message
No mission summary found at ${summaryPath}. What it means
readSummary fails to read the summary file at summaryPath (readFile threw, typically ENOENT). This happens for status/mark/resume/rerun actions that operate on a previously run mission but no summary exists yet.
Source
Thrown at src/cli/mission.ts:290
function resolveMissionPaths(cwd: string, parsed: ParsedMissionArgs): MissionPaths {
const looksLikePath = parsed.file.includes("/") || parsed.file.includes("\\") || parsed.file.endsWith(".md") || parsed.file.endsWith(".txt");
const inputPath = isAbsolute(parsed.file) ? parsed.file : resolve(cwd, parsed.file);
const baseSlug = parsed.slug ?? ((parsed.action === "status" || parsed.action === "mark") && !looksLikePath ? parsed.file : slugify(basename(inputPath, extname(inputPath))));
const slug = slugify(baseSlug);
const missionRoot = join(omxRoot(cwd), "missions", slug);
const summaryPath = parsed.summaryPath
? (isAbsolute(parsed.summaryPath) ? parsed.summaryPath : resolve(cwd, parsed.summaryPath))
: join(missionRoot, "summary.json");
const ledgerPath = join(missionRoot, "ledger.jsonl");
return { inputPath, slug, missionRoot, summaryPath, ledgerPath };
}
async function readSummary(summaryPath: string): Promise<MissionSummary> {
let raw: string;
try {
raw = await readFile(summaryPath, "utf-8");
} catch {
throw new MissionCommandError(`No mission summary found at ${summaryPath}.`);
}
let summary: MissionSummary;
try {
summary = JSON.parse(raw) as MissionSummary;
} catch {
throw new MissionCommandError(`Invalid mission summary at ${summaryPath}.`);
}
if (summary.version !== 1 || !Array.isArray(summary.tasks)) {
throw new MissionCommandError(`Invalid mission summary at ${summaryPath}.`);
}
return summary;
}
function syncSummary(summary: MissionSummary, updates: Partial<Pick<MissionSummary, "status" | "dry_run" | "continue_on_error" | "codex_args">>): void {
Object.assign(summary, updates);
summary.counts = missionCounts(summary.tasks);
}
View on GitHub (pinned to 3ad79a8a6f)
Solutions
- Run `mission run <file>` first to generate the summary
- Verify the expected summary path (derived from the mission slug) exists
- Keep the mission input filename stable so the slug/summary path stays consistent
Example fix
// before await missionCommand(['status', 'mission.md'], options); // after await missionCommand(['run', 'mission.md', '--dry-run'], options); // validate first, then run, then status
Defensive patterns
Strategy: try-catch
Validate before calling
import { access } from 'node:fs/promises';
await access(summaryPath).catch(() => { throw new Error('run the mission first'); }); Try / catch
try { await missionCommand(['status', file], opts); } catch (e) { if (e instanceof MissionCommandError && /No mission summary found/.test(e.message)) await missionCommand(['run', file], opts); else throw e; } Prevention
- Always run before status in CI pipelines
- Keep mission filenames stable so the slug-derived summary path doesn't drift
When it happens
Trigger: Running `mission status <file>` (or mark/resume/rerun) before `mission run <file>` has produced a summary; wrong --summary path or slug derivation pointing at a nonexistent file; summary deleted.
Common situations: Fresh clone where missions haven't run; CI job checking status before the run step; renamed input file changing the derived slug/summary path.
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
- autoresearch_candidate_missing:${candidateFile}
- No autoresearch-goal mission found at ${repoRelative(cwd, pa
- catalog_manifest_missing
- plugin_skill_mirror_out_of_sync\nkind=${mismatch.kind}\n${mi
- [ask] --agent-prompt role "${normalizedRole}" not found in $
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/9394847d92824cbf.
Report an issue: GitHub.