{"record":{"id":"e38317cbc88e9326","repo":"thedotmack/claude-mem","slug":"openrouter-returned-invalid-json","errorCode":null,"errorMessage":"OpenRouter returned invalid JSON","messagePattern":"OpenRouter returned invalid JSON","errorType":"exception","errorClass":"ServerClassifiedProviderError","httpStatus":null,"severity":"error","filePath":"src/server/generation/providers/OpenRouterObservationProvider.ts","lineNumber":110,"sourceCode":"    }\n\n    if (!response.ok) {\n      const bodyText = await safeReadBody(response);\n      throw classifyHttpProviderError({\n        status: response.status,\n        bodyText,\n        headers: response.headers,\n        cause: new Error(`OpenRouter API error: ${response.status} - ${bodyText}`),\n        providerLabel: 'OpenRouter',\n      });\n    }\n\n    let data: OpenRouterResponse;\n    try {\n      data = (await response.json()) as OpenRouterResponse;\n    } catch (parseError) {\n      const err = parseError instanceof Error ? parseError : new Error(String(parseError));\n      throw new ServerClassifiedProviderError('OpenRouter returned invalid JSON', {\n        kind: 'parse_error',\n        cause: err,\n      });\n    }\n\n    if (data.error) {\n      throw classifyHttpProviderError({\n        status: response.status,\n        bodyText: `${data.error.code ?? ''} ${data.error.message ?? ''}`,\n        headers: response.headers,\n        cause: new Error(`OpenRouter API error: ${data.error.code} - ${data.error.message}`),\n        providerLabel: 'OpenRouter',\n      });\n    }\n\n    const rawText = data.choices?.[0]?.message?.content?.trim() ?? '';\n    if (!rawText) {\n      logger.warn('SDK', 'OpenRouter returned empty content', {","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/server/generation/providers/OpenRouterObservationProvider.ts#L92-L128","documentation":"The OpenRouter provider could not deserialize the HTTP response body into OpenRouterResponse during `generate`; `response.json()` threw and the provider re-threw it as a ServerClassifiedProviderError with kind 'parse_error'. OpenRouter sometimes returns non-JSON bodies such as Cloudflare HTML blocks or empty gateway-error pages.","triggerScenarios":"Calling OpenRouterObservationProvider.generate() when the openrouter.ai endpoint returns a non-JSON body: Cloudflare challenge/block pages, empty 502/504 bodies, rate-limit interstitials, or truncated streaming responses.","commonSituations":"Datacenter IP triggering Cloudflare bot protection; OpenRouter incident returning HTML; intermediary proxy stripping or truncating bodies; wrong model id causing an upstream failure surfaced as a non-JSON error page.","solutions":["Log response.status plus the raw body text to see exactly what OpenRouter returned","If Cloudflare blocks appear, route requests from an allowed IP or configure proper User-Agent headers","Check OpenRouter's status page and retry transient 5xx with exponential backoff","Catch kind 'parse_error' ServerClassifiedProviderError and fall back to another provider or surface a user-readable message"],"exampleFix":"// before\ndata = (await response.json()) as OpenRouterResponse;\n\n// after\nconst raw = await response.text();\ntry {\n  data = JSON.parse(raw) as OpenRouterResponse;\n} catch (e) {\n  throw new ServerClassifiedProviderError(`OpenRouter returned non-JSON (status ${response.status}): ${raw.slice(0, 200)}`, { kind: 'parse_error', cause: e as Error });\n}","handlingStrategy":"try-catch","validationCode":"// preflight: verify reachability before real calls\nconst res = await fetch('https://openrouter.ai/api/v1/models');\nif (!res.ok || !(res.headers.get('content-type') ?? '').includes('application/json')) {\n  throw new Error('OpenRouter endpoint not returning JSON; check network/proxy');\n}","typeGuard":"function isOpenRouterResponse(v: unknown): v is OpenRouterResponse {\n  return typeof v === 'object' && v !== null &&\n    ((v as any).error !== undefined || (v as any).choices !== undefined);\n}","tryCatchPattern":"try {\n  const result = await provider.generate(input);\n} catch (e) {\n  if (e instanceof ServerClassifiedProviderError && e.kind === 'parse_error') {\n    logger.warn('OpenRouter returned non-JSON body (likely Cloudflare/gateway)', { cause: e.cause });\n    return fallbackProvider.generate(input);\n  }\n  throw e;\n}","preventionTips":["Log response.status and content-type before parsing to detect Cloudflare/proxy pages","Send a descriptive User-Agent and avoid flagged datacenter IPs to reduce Cloudflare blocks","Implement retry-with-backoff plus a fallback provider for transient gateway failures","Check the OpenRouter status page when parse errors spike"],"tags":["json-parse","openrouter","http-response","provider"],"backgroundTag":"invalid-json-response","analyzedSha":"d8bc9755e74915e5c3b999181e10a67c889bce2a","analyzedAt":"2026-09-17T16:40:26.182Z","contentChangedAt":"2026-09-17T16:40:26.182Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}