{"record":{"id":"8799b8e67347708b","repo":"Yeachan-Heo/oh-my-codex","slug":"source-must-be-valid-json-error-as-error-m","errorCode":null,"errorMessage":"${source} must be valid JSON: ${(error as Error).message}","messagePattern":"(.+?) must be valid JSON: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/cli/state.ts","lineNumber":59,"sourceCode":"function looksLikeQuoteStrippedJson(raw: string): boolean {\n  const trimmed = raw.trim();\n  return trimmed.startsWith('{') && trimmed.endsWith('}') && !trimmed.includes('\"');\n}\n\nfunction parseStateInputJson(\n  raw: string,\n  source: '--input' | '--input-file',\n): Record<string, unknown> {\n  const json = source === '--input-file' && raw.startsWith('\\uFEFF') ? raw.slice(1) : raw;\n  let parsed: unknown;\n  try {\n    parsed = JSON.parse(json);\n  } catch (error) {\n    let message = `${source} must be valid JSON: ${(error as Error).message}`;\n    if (source === '--input' && looksLikeQuoteStrippedJson(raw)) {\n      message += WINDOWS_QUOTE_HINT;\n    }\n    throw new Error(message);\n  }\n  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n    throw new Error(`${source} must decode to a JSON object`);\n  }\n  return { ...(parsed as Record<string, unknown>) };\n}\n\nexport async function stateCommand(\n  args: string[],\n  deps: StateCommandDependencies = {},\n): Promise<void> {\n  const stdout = deps.stdout ?? ((line: string) => console.log(line));\n  const stderr = deps.stderr ?? ((line: string) => console.error(line));\n  const execute = deps.execute ?? executeStateOperation;\n\n  const subcommand = args[0];\n  if (!subcommand || isHelpArg(subcommand)) {\n    stdout(STATE_HELP);","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/cli/state.ts#L41-L77","documentation":"The state command's JSON input (from --input or stdin) failed JSON.parse. The message includes the parser's position info and, for --input on Windows-like shells where quotes get stripped, an extra hint may be appended.","triggerScenarios":"`state set --input '{key: value}'` (unquoted keys), or `--input \"{\"a\":1}\"` on a shell that strips the inner quotes, yielding invalid JSON like {a:1}.","commonSituations":"Windows cmd/PowerShell mangling double quotes; single-quoted heredocs with trailing commas; truncation by terminal paste limits.","solutions":["Validate with a JSON linter or `echo \"$JSON\" | jq .` before passing","On Windows, use the --input-file option with a properly quoted JSON file instead of inline --input","Use jq -c to construct compact, correctly quoted JSON"],"exampleFix":"# before (cmd.exe strips quotes)\nstate set --input \"{\\\"foo\\\": 1}\"\n# after\necho '{\"foo\": 1}' > state.json\nstate set --input-file state.json","handlingStrategy":"validation","validationCode":"function assertJson(text: string, source: string) {\n  try { JSON.parse(text); } catch (e) {\n    throw new Error(`${source} is not valid JSON: ${(e as Error).message}`);\n  }\n}\nassertJson(json, 'payload');","typeGuard":null,"tryCatchPattern":"try { JSON.parse(input); }\ncatch (e) { /* show e.message position, fix quotes/commas */ }","preventionTips":["Build JSON with jq -c rather than string concatenation","On Windows, prefer --input-file over inline --input"],"tags":["cli","json","parsing"],"backgroundTag":"invalid-json","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}