Yeachan-Heo/oh-my-codex · error · UltragoalError
Missing --rationale.
Error message
Missing --rationale.
What it means
Every structured steering proposal must carry non-empty rationale (the reasoning for the mutation), checked right after evidence. It comes from --rationale or the rationale key of --after-json/--directive-json; missing or whitespace-only values abort the steer before steerUltragoal is called.
Source
Thrown at src/cli/ultragoal.ts:462
printStatus(result.plan);
}
return;
}
if (command === 'steer') {
const proposalText = readValue(rest, '--proposal');
const source = readValue(rest, '--source');
if (proposalText) {
const parsed = assertPlainObject(await readJsonInput(proposalText, '--proposal'), '--proposal');
const proposal = normalizeSteeringProposal({ ...parsed, source: source ?? parsed.source }, proposalText);
const result = await steerUltragoal(cwd, proposal, { directiveText: proposalText });
printSteerResult(proposal, result, json);
if (!result.accepted) process.exitCode = 1;
return;
}
const proposal = await parseSteeringProposal(rest);
if (!proposal.evidence?.trim?.()) throw new UltragoalError('Missing --evidence.');
if (!proposal.rationale?.trim?.()) throw new UltragoalError('Missing --rationale.');
proposal.source = parseSteeringSource(source, proposal.source);
const result = await steerUltragoal(cwd, proposal, { directiveText: buildSteerDirectiveText(rest) });
printSteerResult(proposal, result, json);
if (!result.accepted) process.exitCode = 1;
return;
}
if (command === 'record-review-blockers') {
const goalId = readValue(rest, '--goal-id');
const title = readValue(rest, '--title');
const objective = readValue(rest, '--objective');
const evidence = readValue(rest, '--evidence');
if (!goalId) throw new UltragoalError('Missing --goal-id.');
if (!title?.trim()) throw new UltragoalError('Missing --title.');
if (!objective?.trim()) throw new UltragoalError('Missing --objective.');
if (!evidence?.trim()) throw new UltragoalError('Missing --evidence.');
const codexGoal = await parseCodexGoalJson(readValue(rest, '--codex-goal-json'));
const result = await recordFinalReviewBlockers(cwd, { goalId, title, objective, evidence, codexGoal });View on GitHub (pinned to 3ad79a8a6f)
Solutions
- Add `--rationale "..."` explaining the decision logic
- In JSON proposals include a non-empty "rationale" string field
Example fix
# before omx ultragoal steer --kind add_goal --evidence 'ci green' --title t --objective o # Error: Missing --rationale. # after omx ultragoal steer --kind add_goal --evidence 'ci green' --rationale 'follow-up goal needed for docs' --title t --objective o
Defensive patterns
Strategy: validation
Validate before calling
if (!rationale?.trim()) { console.error('steer requires non-empty --rationale'); process.exit(2); } Type guard
function hasRationale(p: { rationale?: string }): boolean {
return typeof p.rationale === 'string' && p.rationale.trim().length > 0;
} Prevention
- Template steer commands with both --evidence and --rationale placeholders
- Programmatic proposals should assert both keys before serialization
When it happens
Trigger: Steering with --kind and --evidence but no --rationale, or a proposal JSON whose rationale key is absent/blank.
Common situations: Believing evidence alone justifies the change; template proposals that only fill kind/evidence; rationale passed under a different key name (e.g. 'reason').
Related errors
- Missing --kind for structured ultragoal steer.
- Missing --evidence.
- omx ultragoal steer rejects broad natural-language mutation
- Missing brief text. Pass --brief, --brief-file, --from-stdin
- Missing --title.
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/45db3b5c0b09aa63.
Report an issue: GitHub.