{"record":{"id":"5c3274384fa80ece","repo":"upstash/context7","slug":"invalid-json-response","errorCode":"invalid_json_response","errorMessage":"Unable to parse response body: ${truncatedBody}","messagePattern":"Unable to parse response body: (.+?)","errorType":"error_code","errorClass":"Context7JSONParseError","httpStatus":null,"severity":"error","filePath":"packages/sdk/src/http/response.ts","lineNumber":73,"sourceCode":"  metadata: Context7ResponseMetadata,\n  retryable: boolean\n): Promise<never> {\n  const rawBody = await response.text();\n  let errorBody: { error?: string; message?: string } = {};\n\n  if (rawBody) {\n    try {\n      const parsed: unknown = JSON.parse(rawBody);\n      if (typeof parsed === \"object\" && parsed !== null && !Array.isArray(parsed)) {\n        const { error, message } = parsed as Record<string, unknown>;\n        errorBody = {\n          error: typeof error === \"string\" ? error : undefined,\n          message: typeof message === \"string\" ? message : undefined,\n        };\n      }\n    } catch (cause) {\n      if (response.headers.get(\"content-type\")?.includes(\"application/json\")) {\n        throw jsonParseError(rawBody, metadata, cause, retryable);\n      }\n    }\n  }\n\n  throw new Context7Error(errorBody.message || errorBody.error || response.statusText, {\n    code: errorBody.error ?? \"http_error\",\n    status: response.status,\n    requestId: metadata.requestId,\n    rateLimit: metadata.rateLimit,\n    retryable,\n  });\n}\n\nasync function parseJson(response: Response, metadata: Context7ResponseMetadata): Promise<unknown> {\n  const rawBody = await response.text();\n  try {\n    return JSON.parse(rawBody);\n  } catch (cause) {","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/upstash/context7/blob/80e681a507c5287bc12e483367c40754e29461b9/packages/sdk/src/http/response.ts#L55-L91","documentation":"Context7JSONParseError raised from throwResponseError: the server returned a non-OK HTTP response whose body was advertised as application/json but whose raw text failed JSON.parse. The library throws this instead of the usual Context7Error so the invalid body is preserved and the mismatch between declared content-type and actual payload is surfaced to the caller. retryable is inherited from whether the status was a transient/retryable status.","triggerScenarios":"Any HttpClient.request call that receives response.ok === false (handled in packages/sdk/src/http/index.ts:94-100) where the error response carries Content-Type: application/json but the body is not valid JSON — e.g. an empty body, an HTML error page served with a JSON content-type, or a truncated response from a proxy.","commonSituations":"API gateway / reverse proxy (nginx, Cloudflare, ALB) returning error pages with a json content-type header; server crashing mid-error-response so the body is cut off; misconfigured server middleware that sets the content-type before serializing nothing; requests routed to a wrong origin that always claims JSON; corporate proxies injecting HTML login pages.","solutions":["Log the error body included in the message (truncatedBody) to see what the server actually returned and identify the interfering proxy or middleware","Inspect the upstream service/proxy for misconfigured error handlers that set Content-Type: application/json on non-JSON error bodies","Retry the request if error.retryable is true (transient status like 502/503/429) — the body may be intact on a subsequent attempt","Check for proxies or VPNs intercepting requests (corporate auth walls returning HTML); bypass or authenticate the proxy","If you control the server, fix the error path to emit valid JSON or drop the application/json content-type when sending plain text"],"exampleFix":"// before: assume all error responses are well-formed JSON\ntry {\n  const res = await client.request({ path: ['v1', 'search'], body: q });\n} catch (e) {\n  console.error(e.message);\n}\n// after: distinguish JSON parse failures on error responses and inspect the body\ntry {\n  const res = await client.request({ path: ['v1', 'search'], body: q });\n} catch (e) {\n  if (e.name === 'Context7JSONParseError') {\n    console.error('Server returned non-JSON error body:', e.message); // shows truncated body\n  } else {\n    throw e;\n  }\n}","handlingStrategy":"try-catch","validationCode":"// cannot inspect the error body before the call, but you can pre-flight the endpoint\nconst probe = await fetch(baseUrl);\nconst ct = probe.headers.get('content-type') ?? '';\nif (!ct.includes('application/json')) console.warn('endpoint not serving JSON');","typeGuard":"function isJsonParseError(e: unknown): e is Context7JSONParseError {\n  return e instanceof Context7JSONParseError;\n}","tryCatchPattern":"try {\n  await client.request({ path: ['v1', 'search'], body: q });\n} catch (e) {\n  if (isJsonParseError(e) && e.retryable) {\n    await sleep(1000);\n    return retryRequest(); // transient status — body may be intact next attempt\n  }\n  if (isJsonParseError(e)) {\n    // inspect e.message for the truncated body; likely proxy/gateway interference\n  }\n  throw e;\n}","preventionTips":["Check error.retryable before retrying; only transient statuses are marked retryable","Inspect the truncated body in the message to spot HTML/proxy injection early","Verify no corporate proxy or VPN intercepts requests to the API host","Alert on this error in production — it usually signals gateway/server misconfiguration, not client bugs"],"tags":["http","json","error-response","proxy"],"backgroundTag":"invalid-json-response","analyzedSha":"80e681a507c5287bc12e483367c40754e29461b9","analyzedAt":"2026-09-08T05:18:41.043Z","contentChangedAt":"2026-09-08T05:18:41.043Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}