paperclipai/paperclip · error · Error
${name} must be a JSON object
Error message
${name} must be a JSON object What it means
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).
Source
Thrown at cli/src/commands/client/approval.ts:254
printOutput(created, { json: ctx.json });
} catch (err) {
handleCommandError(err);
}
}),
);
}
function parseCsv(value: string | undefined): string[] | undefined {
if (!value) return undefined;
const rows = value.split(",").map((v) => v.trim()).filter(Boolean);
return rows.length > 0 ? rows : undefined;
}
function parseJsonObject(value: string, name: string): Record<string, unknown> {
try {
const parsed = JSON.parse(value) as unknown;
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
throw new Error(`${name} must be a JSON object`);
}
return parsed as Record<string, unknown>;
} catch (err) {
throw new Error(`Invalid ${name} JSON: ${err instanceof Error ? err.message : String(err)}`);
}
}
View on GitHub (pinned to 67001ec6eb)
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.
Example fix
// before
paperclipai approval ... --payload '[1,2]'
// surfaces: Invalid payload JSON: payload must be a JSON object
// after
paperclipai approval ... --payload '{"items":[1,2]}' Defensive patterns
Strategy: validation
Validate before calling
// Note: as written, this exact message is unreachable — the catch at [12] rewraps it.
// Validate upstream so neither [11] nor [12] fires:
function asJsonObject(value: string, name: string): Record<string, unknown> {
const parsed = JSON.parse(value) as unknown;
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
throw new Error(`${name} must be a JSON object (got ${Array.isArray(parsed) ? 'array' : typeof parsed})`);
}
return parsed as Record<string, unknown>;
} Type guard
function isPlainObject(v: unknown): v is Record<string, unknown> {
return typeof v === 'object' && v !== null && !Array.isArray(v);
} Try / catch
// Treat the rewrapped message as the real signal:
try { parseJsonObject(opts.value, 'metadata'); }
catch (err) {
const msg = err instanceof Error ? err.message : '';
if (msg.startsWith('Invalid ') && msg.includes('must be a JSON object')) {
console.error('Pass the option as a JSON object, e.g. --metadata \'{}\'');
process.exit(2);
}
throw err;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Invalid ${name} JSON: ${err instanceof Error ? err.message :
- --payload must be a JSON object
- Invalid JSON: ${err instanceof Error ? err.message : String(
- Failed to parse JSON at ${filePath}: ${err instanceof Error
- --file is required
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/a71cf6f2ee5960b9.
Report an issue: GitHub.