Yeachan-Heo/oh-my-codex · error · Error
--handoff-json must resolve to a JSON object.
Error message
--handoff-json must resolve to a JSON object.
What it means
Thrown by jsonInput() when the --handoff-json argument (inline string or file path) parses to something other than a plain JSON object. The autopilot advance command requires a handoff payload that is a JSON object; arrays, strings, numbers, null, or empty input all fail this check.
Source
Thrown at src/cli/autopilot.ts:51
const result = args[index + 1];
if (!result || result.startsWith('--')) throw new Error(`Missing value for ${flag}.`);
return result;
}
function positionalTask(args: readonly string[]): string {
const valueFlags = new Set(['--task', '--session', '--to', '--handoff-json']);
const words: string[] = [];
for (let i = 0; i < args.length; i += 1) {
if (valueFlags.has(args[i])) { i += 1; continue; }
if (!args[i].startsWith('--')) words.push(args[i]);
}
return words.join(' ').trim();
}
async function jsonInput(raw: string): Promise<Record<string, unknown>> {
const text = raw.trim().startsWith('{') ? raw : await readFile(raw, 'utf-8');
const parsed = JSON.parse(text) as unknown;
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error('--handoff-json must resolve to a JSON object.');
return parsed as Record<string, unknown>;
}
function assertBoundHandoffIdentity(handoff: Record<string, unknown>, cwd: string, sessionId?: string): void {
if (typeof handoff.session_id === 'string' && handoff.session_id !== sessionId) {
throw new Error('Autopilot handoff session_id does not match the selected session.');
}
if (typeof handoff.workingDirectory === 'string' && handoff.workingDirectory !== cwd) {
throw new Error('Autopilot handoff workingDirectory does not match the selected workspace.');
}
handoff.session_id = sessionId;
handoff.workingDirectory = cwd;
}
async function readAutopilot(cwd: string, sessionId?: string) {
return sessionId
? readModeStateForExplicitSession('autopilot', sessionId, cwd)
: readModeState('autopilot', cwd);View on GitHub (pinned to 3ad79a8a6f)
Solutions
- Check the handoff payload with `cat <file>` or paste it into a JSON validator and confirm the top-level value is an object `{...}`, not an array or string
- If the payload is an array, wrap it: {"steps": [...]}, or restructure to the expected handoff schema (task, session_id, workingDirectory keys)
- If passing inline, ensure the argument starts with `{` — otherwise it is treated as a file path and readFile fails or reads wrong content
- Confirm the file path exists and is readable so you are validating the intended payload
Example fix
// before
omx autopilot advance --to ralplan --handoff-json '[{"step":1}]'
// after
omx autopilot advance --to ralplan --handoff-json '{"steps":[{"step":1}]}'} Defensive patterns
Strategy: validation
Validate before calling
function isJsonObjectFile(s: string): boolean {
const text = s.trim().startsWith('{') ? s : require('fs').readFileSync(s, 'utf-8');
const p = JSON.parse(text);
return !!p && typeof p === 'object' && !Array.isArray(p);
}
if (!isJsonObjectFile(args.handoffJson)) failFast('handoff must be a JSON object'); Type guard
function isJsonObject(v: unknown): v is Record<string, unknown> {
return !!v && typeof v === 'object' && !Array.isArray(v);
} Try / catch
try { await autopilotCommand(['advance','--to','ralplan','--handoff-json',p]); } catch (e) { if (e.message.includes('must resolve to a JSON object')) printPayloadError(p); else throw e; } Prevention
- Validate handoff files with a JSON schema before invoking the CLI
- Keep handoff writers typed as Record<string, unknown> so arrays can't be emitted
When it happens
Trigger: Calling `omx autopilot advance --handoff-json <x>` where x is either an inline string not starting with '{' whose file content parses to a non-object (e.g. a JSON array or string), or a path to a file containing `[...]`, `"text"`, `123`, or invalid JSON (JSON.parse would throw first for invalid JSON; this error fires for valid-but-non-object JSON).
Common situations: Agent pipelines writing a JSON array of steps as the handoff, saving a handoff exported from another tool that wraps it in quotes, or passing an empty file. Also hitting this when JSON.parse succeeds on a top-level scalar.
Related errors
- Autopilot handoff session_id does not match the selected ses
- Autopilot handoff workingDirectory does not match the select
- Missing value for ${flag}.
- Missing --task.
- No Autopilot state found. Run `omx autopilot start --task <t
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/0ca33ab4a4f8d47a.
Report an issue: GitHub.