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

  1. Run `mission run <file>` first to generate the summary
  2. Verify the expected summary path (derived from the mission slug) exists
  3. 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

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


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