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

Invalid ${label}: ${message}

Error message

Invalid ${label}: ${message}

What it means

readJsonInput parses values for flags like --directive-json and --after-json. It accepts either inline JSON (value starting with { or [) or a filesystem path to a JSON file. If neither parses — bad JSON syntax or an unreadable/missing file — it throws UltragoalError labeled with the flag name and the underlying parse error message.

Source

Thrown at src/cli/ultragoal.ts:185

    `Required external decision: ${blocked.requiredExternalDecision ?? 'provide the missing authorization/credential, or explicitly choose a different unblock path'}.`,
    'Do not run complete-goals --retry-failed again until that external state changes or the user explicitly authorizes an unblock path.',
  ].join('\n');
}

async function parseCodexGoalJson(raw: string | undefined): Promise<unknown> {
  if (!raw) return undefined;
  return readCodexGoalSnapshotInput(raw, process.cwd());
}

async function readJsonInput(raw: string | undefined, label = '--quality-gate-json'): Promise<unknown> {
  if (!raw) return undefined;
  try {
    const trimmed = raw.trim();
    if (trimmed.startsWith('{') || trimmed.startsWith('[')) return JSON.parse(trimmed);
    return JSON.parse(await readFile(trimmed, 'utf-8'));
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    throw new UltragoalError(`Invalid ${label}: ${message}`);
  }
}

const NATIVE_SUBAGENT_CAPACITY_BLOCKER_FILE = 'native-subagent-capacity-blocker.json';

async function readJsonIfExists<T>(path: string): Promise<T | null> {
  try {
    return JSON.parse(await readFile(path, 'utf-8')) as T;
  } catch {
    return null;
  }
}

async function resolveCodexGoalNativeSubagentSupport(cwd: string) {
  const scope = await resolveRuntimeStateScope(cwd);
  const [persistedSupportBlocker, persistedCapacityBlocker] = await Promise.all([
    readJsonIfExists<Record<string, unknown>>(join(scope.baseStateDir, NATIVE_SUBAGENT_SUPPORT_BLOCKER_FILE)),
    readJsonIfExists<Record<string, unknown>>(join(scope.baseStateDir, NATIVE_SUBAGENT_CAPACITY_BLOCKER_FILE)),

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Validate the JSON with `jq . file.json` (or `echo '...' | jq .`) to find the syntax error, then fix it
  2. If passing a path, confirm it exists and is readable from the CLI's working directory (use an absolute path)
  3. For inline JSON prefer single-quoted shell strings to avoid interpolation breaking quotes

Example fix

# before
omx ultragoal steer --after-json '{"kind":"add_goal",}'
# Error: Invalid --after-json: Unexpected token } in JSON at position 18

# after
omx ultragoal steer --after-json '{"kind":"add_goal"}'
Defensive patterns

Strategy: validation

Validate before calling

function parseJsonArg(raw: string, label: string): unknown {
  const t = raw.trim();
  const text = t.startsWith('{') || t.startsWith('[') ? t : readFileSync(t, 'utf-8');
  return JSON.parse(text); // throws here first, with clearer context
}
// pre-validate before calling the CLI programmatically:
try { parseJsonArg(afterJson, '--after-json'); } catch { console.error('fix JSON before running omx'); }

Try / catch

try { await ultragoalCommand(...) } catch (e) { if (e instanceof UltragoalError && e.message.startsWith('Invalid --')) showJsonDiagnostics(raw); else throw e; }

Prevention

When it happens

Trigger: Passing `--after-json '{"kind":"x",}'` (trailing comma), `--directive-json missing.json` (ENOENT), or a path whose contents are not valid JSON; also permission errors reading the file.

Common situations: Hand-writing JSON on the shell with quoting mistakes; relative path resolved against an unexpected cwd; file generated by another tool with BOM or trailing text; single-quote heredoc issues.

Related errors


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