{"record":{"id":"a71cf6f2ee5960b9","repo":"paperclipai/paperclip","slug":"name-must-be-a-json-object","errorCode":null,"errorMessage":"${name} must be a JSON object","messagePattern":"(.+?) must be a JSON object","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"cli/src/commands/client/approval.ts","lineNumber":254,"sourceCode":"          printOutput(created, { json: ctx.json });\n        } catch (err) {\n          handleCommandError(err);\n        }\n      }),\n  );\n}\n\nfunction parseCsv(value: string | undefined): string[] | undefined {\n  if (!value) return undefined;\n  const rows = value.split(\",\").map((v) => v.trim()).filter(Boolean);\n  return rows.length > 0 ? rows : undefined;\n}\n\nfunction parseJsonObject(value: string, name: string): Record<string, unknown> {\n  try {\n    const parsed = JSON.parse(value) as unknown;\n    if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n      throw new Error(`${name} must be a JSON object`);\n    }\n    return parsed as Record<string, unknown>;\n  } catch (err) {\n    throw new Error(`Invalid ${name} JSON: ${err instanceof Error ? err.message : String(err)}`);\n  }\n}\n","sourceCodeStart":236,"sourceCodeEnd":261,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/cli/src/commands/client/approval.ts#L236-L261","documentation":"DEAD/UNREACHABLE MESSAGE as written. parseJsonObject() in approval.ts throws `${name} must be a JSON object` INSIDE the try block, but the surrounding catch (see [12]) catches every error from the try — including this one — and re-wraps it as `Invalid ${name} JSON: <originalMessage>`. So the user actually observes `Invalid ${name} JSON: ${name} must be a JSON object`, never the bare [11] message. Conceptually it represents: the --<name> option parsed as JSON but the result was not a plain object (was array/primitive).","triggerScenarios":"Passing an approval option like --payload or similar that is valid JSON but not an object (array/number/string/boolean). Due to the catch, the surfaced message is the [12] form with this text appended.","commonSituations":"Same shape as [10]: user passed a JSON array or primitive where the approval command requires an object. The visible symptom is the rewrapped message, which can obscure the real cause slightly.","solutions":["Pass a JSON object: --<name> '{\"key\":\"value\"}'.","Recognise the surfaced message will be `Invalid <name> JSON: <name> must be a JSON object` — treat it as 'not an object'.","Consider filing a repo issue: the inner throw is shadowed by the catch; the two error paths should be separated for clarity."],"exampleFix":"// before\npaperclipai approval ... --payload '[1,2]'\n// surfaces: Invalid payload JSON: payload must be a JSON object\n// after\npaperclipai approval ... --payload '{\"items\":[1,2]}'","handlingStrategy":"validation","validationCode":"// Note: as written, this exact message is unreachable — the catch at [12] rewraps it.\n// Validate upstream so neither [11] nor [12] fires:\nfunction asJsonObject(value: string, name: string): Record<string, unknown> {\n  const parsed = JSON.parse(value) as unknown;\n  if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n    throw new Error(`${name} must be a JSON object (got ${Array.isArray(parsed) ? 'array' : typeof parsed})`);\n  }\n  return parsed as Record<string, unknown>;\n}","typeGuard":"function isPlainObject(v: unknown): v is Record<string, unknown> {\n  return typeof v === 'object' && v !== null && !Array.isArray(v);\n}","tryCatchPattern":"// Treat the rewrapped message as the real signal:\ntry { parseJsonObject(opts.value, 'metadata'); }\ncatch (err) {\n  const msg = err instanceof Error ? err.message : '';\n  if (msg.startsWith('Invalid ') && msg.includes('must be a JSON object')) {\n    console.error('Pass the option as a JSON object, e.g. --metadata \\'{}\\'');\n    process.exit(2);\n  }\n  throw err;\n}","preventionTips":["Always pass --<name> as a JSON object literal.","Consider filing a repo issue: the inner throw at [11] is shadowed by the catch at [12].","Validate with jq before passing complex JSON."],"tags":["cli","approval","json","validation","dead-code"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}