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

  1. Pass a JSON object: --<name> '{"key":"value"}'.
  2. Recognise the surfaced message will be `Invalid <name> JSON: <name> must be a JSON object` — treat it as 'not an object'.
  3. 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

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


AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12). Data as JSON: /api/errors/a71cf6f2ee5960b9. Report an issue: GitHub.