Yeachan-Heo/oh-my-codex · error · UltragoalError
${label} must be a JSON object.
Error message
${label} must be a JSON object. What it means
assertPlainObject guards parsed JSON inputs (notably --directive-json) so the steering engine always receives a JSON object, never an array, scalar, or null. It is used with labels like '--directive-json' so the message says which input failed.
Source
Thrown at src/cli/ultragoal.ts:235
const STEERING_KINDS = new Set<UltragoalSteeringMutationKind>(ULTRAGOAL_STEERING_MUTATION_KINDS);
const STEERING_SOURCES = new Set<UltragoalSteeringSource>(ULTRAGOAL_STEERING_SOURCES);
type CliSteerResult = Awaited<ReturnType<typeof steerUltragoal>>;
function parseSteeringKind(raw: string | undefined): UltragoalSteeringMutationKind {
if (!raw) throw new UltragoalError('Missing --kind for structured ultragoal steer.');
if (!STEERING_KINDS.has(raw as UltragoalSteeringMutationKind)) throw new UltragoalError(`Invalid --kind: ${raw}. Expected one of ${Array.from(STEERING_KINDS).join(', ')}.`);
return raw as UltragoalSteeringMutationKind;
}
function parseSteeringSource(raw: string | undefined, fallback: UltragoalSteeringSource = 'cli'): UltragoalSteeringSource {
if (!raw) return fallback;
if (!STEERING_SOURCES.has(raw as UltragoalSteeringSource)) throw new UltragoalError(`Invalid --source: ${raw}. Expected one of ${Array.from(STEERING_SOURCES).join(', ')}.`);
return raw as UltragoalSteeringSource;
}
function assertPlainObject(value: unknown, label: string): Record<string, unknown> {
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new UltragoalError(`${label} must be a JSON object.`);
return value as Record<string, unknown>;
}
function normalizeTargetGoalId(raw: Record<string, unknown>): string | undefined {
if (typeof raw.targetGoalId === 'string' && raw.targetGoalId.trim()) return raw.targetGoalId.trim();
if (Array.isArray(raw.targetGoalIds)) return raw.targetGoalIds.find((id): id is string => typeof id === 'string' && id.trim().length > 0)?.trim();
return undefined;
}
function normalizeSteeringProposal(raw: Record<string, unknown>, _fallbackDirectiveText?: string): UltragoalSteeringProposal {
const kind = parseSteeringKind(typeof raw.kind === 'string' ? raw.kind : undefined);
const source = parseSteeringSource(typeof raw.source === 'string' ? raw.source : undefined);
const after = raw.after && typeof raw.after === 'object' && !Array.isArray(raw.after)
? raw.after as UltragoalSteeringAfterPayload
: undefined;
return {
kind,
source,View on GitHub (pinned to 3ad79a8a6f)
Solutions
- Wrap the payload in an object: `{"proposals":[...]}` or send one proposal object with kind/evidence/rationale fields
- Validate with `jq 'type'` — it must print "object" before passing the file
Example fix
# before
omx ultragoal steer --directive-json '[{"kind":"add_goal"}]'
# Error: --directive-json must be a JSON object.
# after
omx ultragoal steer --directive-json '{"kind":"add_goal","evidence":"e","rationale":"r"}' Defensive patterns
Strategy: type-guard
Validate before calling
const parsed = JSON.parse(directiveJson);
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
console.error('--directive-json must be an object, not an array/scalar'); process.exit(2);
} Type guard
function isPlainObject(v: unknown): v is Record<string, unknown> {
return !!v && typeof v === 'object' && !Array.isArray(v);
} Prevention
- Check `jq 'type' == "object"` before passing files
- Build directives with JSON.stringify of an object literal, never an array
When it happens
Trigger: Passing `--directive-json '[1,2]'`, `--directive-json '"text"'`, or `--directive-json 'null'` to `omx ultragoal steer`; a JSON file containing an array also triggers it.
Common situations: Reusing an exported array of proposals instead of a single object; tools emitting a top-level array; JSON files that wrap payloads in arrays by convention.
Related errors
- ${source} must decode to a JSON object
- Invalid ${label}: ${message}
- Invalid --input JSON: ${error instanceof Error ? error.messa
- ${source} must be valid JSON: ${(error as Error).message}
- Missing JSON value after --input
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/31d1e7ee64b1fb79.
Report an issue: GitHub.