musistudio/claude-code-router · error · Error

Provider manifest must be a JSON object.

Error message

Provider manifest must be a JSON object.

What it means

parseProviderManifestPayload expects the fetched manifest document to be a JSON object. If the parsed value is an array, a string, a number, null, or a primitive, isRecord(value) fails and this error is thrown before provider-field extraction.

Source

Thrown at packages/core/src/contracts/deep-link.ts:182

  );
  return {
    ...(account ? { account } : {}),
    ...(apiKey ? { apiKey } : {}),
    baseUrl,
    ...(icon ? { icon } : {}),
    ...(modelDescriptions ? { modelDescriptions } : {}),
    ...(modelDisplayNames ? { modelDisplayNames } : {}),
    ...(modelMetadata ? { modelMetadata } : {}),
    models,
    ...(name ? { name } : {}),
    ...(protocol ? { protocol } : {}),
    ...(source ? { source } : {})
  };
}

export function parseProviderManifestPayload(value: unknown, sourceUrl?: string): ProviderDeepLinkPayload {
  if (!isRecord(value)) {
    throw new Error("Provider manifest must be a JSON object.");
  }
  const providerValue = isRecord(value.provider)
    ? value.provider
    : isRecord(value.ccrProvider)
      ? value.ccrProvider
      : value;
  return parseProviderPayloadFields(new URLSearchParams(), providerValue, sourceUrl);
}

function parseProviderPayloadFields(
  params: URLSearchParams,
  payload: Record<string, unknown> | undefined,
  sourceFallback?: string
): ProviderDeepLinkPayload {
  const name = boundedString(
    firstStringParam(params, ["name"]) ??
      firstPayloadString(payload, ["name"]),
    maxNameLength,

View on GitHub (pinned to 99f24806c6)

Solutions

  1. Make the manifest a top-level JSON object (wrap arrays under a key, e.g. {"provider": {...}})
  2. Check what the manifest URL actually returns (curl it) and fix the document shape
  3. If the manifest legitimately uses ccrProvider key, keep it as {"ccrProvider": {...}} — still an object

Example fix

// before
[{"name":"acme","base_url":"https://api.acme.dev"}]
// after
{"provider":{"name":"acme","base_url":"https://api.acme.dev"}}
Defensive patterns

Strategy: type-guard

Validate before calling

const parsed = JSON.parse(text); if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return rejectManifest();

Type guard

const isRecord = (v: unknown): v is Record<string, unknown> =>
  typeof v === "object" && v !== null && !Array.isArray(v);

Try / catch

try { parseProviderManifestPayload(parsed); } catch (e) { if (e instanceof Error && e.message === "Provider manifest must be a JSON object.") return logManifestShape(parsed); throw e; }

Prevention

When it happens

Trigger: Calling parseProviderManifestPayload(value) where value is e.g. a JSON array of providers, a bare string, or the JSON.parse result of an error page body.

Common situations: Manifest endpoint returning a top-level array or a plain string; server returning an error/HTML body that was JSON.parse'd elsewhere; hand-authored manifest file with the provider nested but the file itself is a list.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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