{"record":{"id":"32fb87b1784a2471","repo":"Yeachan-Heo/oh-my-codex","slug":"mission-execution-requires-a-task-runner-use-dr","errorCode":null,"errorMessage":"Mission execution requires a task runner; use --dry-run for parser/plan validation.","messagePattern":"Mission execution requires a task runner; use --dry-run for parser/plan validation\\.","errorType":"validation","errorClass":"MissionCommandError","httpStatus":null,"severity":"error","filePath":"src/cli/mission.ts","lineNumber":472,"sourceCode":"\n    if (parsed.dryRun) {\n      for (const task of summary.tasks) task.status = \"planned\";\n      summary.counts = missionCounts(summary.tasks);\n      summary.completed_at = now().toISOString();\n      await persistSummary(paths.summaryPath, summary);\n      await appendLedger(paths.ledgerPath, { event: \"mission_planned\", at: summary.completed_at, slug: paths.slug, total: tasks.length, summary_path: paths.summaryPath });\n      if (parsed.json) stdout(JSON.stringify({ ok: true, summary_path: paths.summaryPath, ledger_path: paths.ledgerPath, summary }, null, 2));\n      else {\n        stdout(`mission planned: ${paths.slug} (${tasks.length} tasks)`);\n        for (const task of summary.tasks) stdout(`[planned] ${task.id} line ${task.source_line}: ${task.prompt}`);\n        stdout(`summary: ${paths.summaryPath}`);\n        stdout(`ledger: ${paths.ledgerPath}`);\n      }\n      return;\n    }\n\n    const runTask = options.runTask;\n    if (!runTask) throw new MissionCommandError(\"Mission execution requires a task runner; use --dry-run for parser/plan validation.\");\n\n    await persistSummary(paths.summaryPath, summary);\n    await appendLedger(paths.ledgerPath, { event: \"mission_started\", at: startedAt, slug: paths.slug, total: tasks.length, summary_path: paths.summaryPath });\n    await runSelectedTasks(summary, paths, parsed, now, stdout, runTask, () => true);\n\n    summary.status = missionStatus(summary.tasks);\n    summary.completed_at = now().toISOString();\n    summary.counts = missionCounts(summary.tasks);\n    await persistSummary(paths.summaryPath, summary);\n    await appendLedger(paths.ledgerPath, { event: \"mission_completed\", at: summary.completed_at, slug: paths.slug, status: summary.status, counts: summary.counts });\n\n    if (parsed.json) stdout(JSON.stringify({ ok: summary.status === \"passed\", summary_path: paths.summaryPath, ledger_path: paths.ledgerPath, summary }, null, 2));\n    else {\n      stdout(`mission ${summary.status}: ${paths.slug}`);\n      stdout(`summary: ${paths.summaryPath}`);\n      stdout(`ledger: ${paths.ledgerPath}`);\n    }\n    if (summary.status === \"failed\") process.exitCode = 1;","sourceCodeStart":454,"sourceCodeEnd":490,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/cli/mission.ts#L454-L490","documentation":"During a real (non-dry-run) mission run, options.runTask is undefined. Actually-executing runs need a caller-supplied task runner; --dry-run paths return earlier without one.","triggerScenarios":"Calling missionCommand(['run', file], {}) without runTask and without --dry-run — the run reaches the execution stage and has no way to execute tasks.","commonSituations":"Host applications embedding the CLI without wiring the runner; tests that omit the runner stub; refactors dropping the option.","solutions":["Provide options.runTask when invoking run/resume/rerun programmatically","Add --dry-run if you only need parser/plan validation","Delegate to the actual CLI entrypoint which supplies the default runner"],"exampleFix":"// before\nawait missionCommand(['run', 'mission.md'], { stdout });\n// after\nawait missionCommand(['run', 'mission.md'], { stdout, runTask: runCodexTask });","handlingStrategy":"validation","validationCode":"if (action === 'run' && !parsed.dryRun && typeof options.runTask !== 'function') failEarly('runner required');","typeGuard":"const hasRunner = (o: MissionOptions): o is MissionOptions & { runTask: RunTask } =>\n  typeof o.runTask === 'function';","tryCatchPattern":null,"preventionTips":["Default wrappers to --dry-run when no runner is configured"],"tags":["mission","missing-runner","embedding"],"backgroundTag":"missing-dependency-callback","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}