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

Mission execution requires a task runner; use status for rea

Error message

Mission execution requires a task runner; use status for read-only inspection.

What it means

For mission resume/rerun, the command was invoked without a runTask function in options. Execution actions require a caller-supplied task runner; only status is available without one.

Source

Thrown at src/cli/mission.ts:400

      task.status = parsed.markStatus ?? task.status;
      task.completed_at = now().toISOString();
      delete task.exit_code;
      syncSummary(summary, { status: missionStatus(summary.tasks) });
      await persistSummary(paths.summaryPath, summary);
      await appendLedger(paths.ledgerPath, { event: "task_marked", at: task.completed_at, slug: summary.slug, task_id: task.id, status: task.status });
      if (parsed.json) stdout(JSON.stringify({ ok: true, summary_path: paths.summaryPath, ledger_path: paths.ledgerPath, summary }, null, 2));
      else {
        stdout(`mission task marked ${task.status}: ${summary.slug} ${task.id}`);
        stdout(`summary: ${paths.summaryPath}`);
        stdout(`ledger: ${paths.ledgerPath}`);
      }
      return;
    }

    if (parsed.action === "resume" || parsed.action === "rerun") {
      const summary = await readSummary(paths.summaryPath);
      const runTask = options.runTask;
      if (!runTask) throw new MissionCommandError("Mission execution requires a task runner; use status for read-only inspection.");
      if (parsed.action === "rerun" && !summary.tasks.some((task) => task.id === parsed.taskId)) {
        throw new MissionCommandError(`No mission task found for --task ${parsed.taskId}.`);
      }

      summary.input_path = paths.inputPath;
      summary.dry_run = false;
      summary.continue_on_error = parsed.continueOnError;
      summary.codex_args = parsed.codexArgs;
      summary.status = "running";
      delete summary.completed_at;
      for (const task of summary.tasks) {
        if (task.status === "running") task.status = "pending";
      }
      syncSummary(summary, { status: "running" });
      await persistSummary(paths.summaryPath, summary);
      await appendLedger(paths.ledgerPath, { event: parsed.action === "resume" ? "mission_resumed" : "mission_rerun_started", at: now().toISOString(), slug: summary.slug, summary_path: paths.summaryPath, task_id: parsed.taskId });

      const shouldRun = parsed.action === "resume"

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Pass options.runTask: a function that executes a task and returns its exit code
  2. Use mission status when no runner is available
  3. For plan-only validation, use run --dry-run with a runner or the parser directly

Example fix

// before
await missionCommand(['resume', 'mission.md'], { stdout });
// after
await missionCommand(['resume', 'mission.md'], { stdout, runTask: async (task) => spawnRunner(task) });
Defensive patterns

Strategy: validation

Validate before calling

if ((action === 'resume' || action === 'rerun') && typeof options.runTask !== 'function') {
  failEarly('no task runner configured');
}

Type guard

const hasRunner = (o: MissionOptions): o is MissionOptions & { runTask: RunTask } =>
  typeof o.runTask === 'function';

Prevention

When it happens

Trigger: Calling missionCommand(['resume', file], {}) or with options.runTask undefined — e.g. embedding the CLI in a host that only wires up read-only inspection.

Common situations: Library embedding where the host deliberately omits the runner; refactoring that dropped the runTask option; tests invoking missionCommand without a stub runner.

Related errors


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