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

omx ultragoal steer rejects broad natural-language mutation

Error message

omx ultragoal steer rejects broad natural-language mutation requests; pass structured fields or --directive-json.

What it means

ultragoal steer deliberately refuses freeform natural-language positional text as a mutation request. Because mutations are leader-owned and auditable, the CLI only accepts structured fields (--kind/--evidence/--rationale/etc.), an --after-json proposal, or a full --directive-json document.

Source

Thrown at src/cli/ultragoal.ts:274

    targetGoalId: normalizeTargetGoalId(raw),
    title: typeof raw.title === 'string' ? raw.title : undefined,
    objective: typeof raw.objective === 'string' ? raw.objective : undefined,
    after,
    pendingOrder: Array.isArray(raw.pendingOrder) ? raw.pendingOrder.filter((id): id is string => typeof id === 'string') : undefined,
    idempotencyKey: typeof raw.idempotencyKey === 'string' ? raw.idempotencyKey : undefined,
  };
}

async function parseSteeringProposal(args: readonly string[]): Promise<UltragoalSteeringProposal> {
  const directiveFile = readValue(args, '--directive-file');
  const directiveRaw = readValue(args, '--directive-json') ?? (directiveFile ? await readFile(directiveFile, 'utf-8') : undefined);
  if (directiveRaw) {
    const directive = assertPlainObject(await readJsonInput(directiveRaw, '--directive-json'), '--directive-json');
    return normalizeSteeringProposal(directive, directiveRaw.trim().startsWith('{') ? directiveRaw : undefined);
  }

  const freeform = positionalText(args);
  if (freeform) throw new UltragoalError('omx ultragoal steer rejects broad natural-language mutation requests; pass structured fields or --directive-json.');

  const after = await readJsonInput(readValue(args, '--after-json'), '--after-json');
  return normalizeSteeringProposal({
    kind: readValue(args, '--kind'),
    evidence: readValue(args, '--evidence'),
    rationale: readValue(args, '--rationale'),
    targetGoalId: readValue(args, '--target-goal-id') ?? readValue(args, '--goal-id'),
    title: readValue(args, '--title'),
    objective: readValue(args, '--objective'),
    after,
    idempotencyKey: readValue(args, '--idempotency-key'),
  });
}

function buildSteerDirectiveText(args: readonly string[]): string {
  return args.join(' ');
}

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Convert the intent into structured flags: --kind, --evidence, --rationale plus kind-specific fields
  2. Use --after-json with a proposal object
  3. Use --directive-json with a complete directive document for complex multi-field mutations

Example fix

# before
omx ultragoal steer "pause the api goal"
# Error: omx ultragoal steer rejects broad natural-language mutation requests...

# after
omx ultragoal steer --kind update_goal --goal-id api-1 --status paused --evidence 'flaky ci' --rationale 'pause until green'
Defensive patterns

Strategy: validation

Validate before calling

const args = ['pause the api goal']; // freeform
if (args.length && !hasDirectiveJson) {
  // convert intent to structured flags instead of passing prose
  throw new Error('convert freeform text to --kind/--evidence/--rationale or --directive-json');
}

Prevention

When it happens

Trigger: Running `omx ultragoal steer "please pause all goals"` — i.e. supplying positional freeform text without --directive-json; the code detects positionalText(args) and throws.

Common situations: Habit from other AI CLIs that accept prose commands; older workflows before structured steering was enforced; scripts written against a previous freeform interface.

Related errors


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