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

  1. 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
  2. If the payload is an array, wrap it: {"steps": [...]}, or restructure to the expected handoff schema (task, session_id, workingDirectory keys)
  3. If passing inline, ensure the argument starts with `{` — otherwise it is treated as a file path and readFile fails or reads wrong content
  4. 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

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


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