musistudio/claude-code-router · error · Error

Sample must be a JSON object with an object body

Error message

Sample must be a JSON object with an object body

What it means

Thrown by normalizeRouteScriptSampleRequest when validating a route script sample request payload. The function requires the incoming value to be a plain JSON object that itself contains a `body` property which is also a plain object. Any non-object input (string, array, null, or missing/non-object body) is rejected before normalization proceeds.

Source

Thrown at packages/ui/src/pages/home/shared/routing.ts:178

  if (language && language !== "javascript" && language !== "js") return undefined;
  const apiVersion = Number(value.apiVersion ?? value.version ?? ROUTER_SCRIPT_API_VERSION);
  if (apiVersion !== ROUTER_SCRIPT_API_VERSION) return undefined;
  const rawTimeout = Number(value.timeoutMs ?? value.timeout ?? ROUTER_SCRIPT_DEFAULT_TIMEOUT_MS);
  const timeoutMs = Number.isFinite(rawTimeout)
    ? Math.max(10, Math.min(ROUTER_SCRIPT_MAX_TIMEOUT_MS, Math.trunc(rawTimeout)))
    : ROUTER_SCRIPT_DEFAULT_TIMEOUT_MS;
  return {
    apiVersion: ROUTER_SCRIPT_API_VERSION,
    ...(file ? { file } : {}),
    language: "javascript",
    ...(source !== undefined ? { source } : {}),
    timeoutMs
  };
}

export function normalizeRouteScriptSampleRequest(value: unknown): RouteScriptSampleRequest {
  if (!isPlainRecord(value) || !isPlainRecord(value.body)) {
    throw new Error("Sample must be a JSON object with an object body");
  }
  const headers = normalizeRouteScriptSampleHeaders(value.headers);
  return {
    body: value.body,
    headers,
    ...(typeof value.method === "string" ? { method: value.method } : {}),
    ...(typeof value.sessionId === "string" ? { sessionId: value.sessionId } : {}),
    ...(typeof value.tokenCount === "number" ? { tokenCount: value.tokenCount } : {}),
    ...(typeof value.url === "string" ? { url: value.url } : {})
  };
}

function normalizeRouteScriptSampleHeaders(value: unknown): Record<string, string | string[]> {
  if (value === undefined) return {};
  if (!isPlainRecord(value)) {
    throw new Error("Sample headers must be a JSON object containing string or string-array values");
  }
  const headers: Record<string, string | string[]> = {};

View on GitHub (pinned to 99f24806c6)

Solutions

  1. Ensure the sample is a parsed object with an object body: { body: {...} }
  2. If the sample arrives as a string, JSON.parse it before calling the API
  3. For empty bodies, pass an explicit empty object: { body: {}, ... }

Example fix

// before
request(JSON.stringify({ body: { a: 1 } }))

// after
request({ body: { a: 1 } })
Defensive patterns

Strategy: type-guard

Validate before calling

if (typeof sample !== "object" || sample === null || Array.isArray(sample) || typeof sample.body !== "object" || sample.body === null || Array.isArray(sample.body)) { /* fix input before calling */ }

Type guard

function isRouteScriptSample(v: unknown): v is { body: Record<string, unknown>; headers?: unknown; method?: unknown } {
  return typeof v === "object" && v !== null && !Array.isArray(v)
    && typeof (v as any).body === "object" && (v as any).body !== null && !Array.isArray((v as any).body);
}

Try / catch

try { normalizeRouteScriptSampleRequest(sample); } catch (e) { if (e instanceof Error && e.message.includes("JSON object with an object body")) return badRequest(e.message); throw e; }

Prevention

When it happens

Trigger: Calling request() (route script sampling) with a JSON string instead of a parsed object, an array, null, or an object whose `body` is missing, a string, a number, or an array.

Common situations: Passing JSON.stringify()'d sample data without JSON.parse, building the sample from a form where body was left as a raw text string, or sending an empty object {} because the request had no body configured.

Related errors


AI-assisted analysis of musistudio/claude-code-router@99f24806c6 (2026-08-27). Data as JSON: /api/errors/6beacd15e23208a8. Report an issue: GitHub.