{"record":{"id":"0de29a7bdf61354d","repo":"thedotmack/claude-mem","slug":"parse-error","errorCode":"parse_error","errorMessage":"Anthropic returned invalid JSON","messagePattern":"Anthropic returned invalid JSON","errorType":"exception","errorClass":"ServerClassifiedProviderError","httpStatus":null,"severity":"error","filePath":"src/server/generation/providers/ClaudeObservationProvider.ts","lineNumber":98,"sourceCode":"      });\n    }\n\n    if (!response.ok) {\n      const bodyText = await safeReadBody(response);\n      throw classifyClaudeServerError({\n        status: response.status,\n        bodyText,\n        headers: response.headers,\n        cause: new Error(`Anthropic API error: ${response.status} - ${bodyText}`),\n      });\n    }\n\n    let data: AnthropicMessagesResponse;\n    try {\n      data = (await response.json()) as AnthropicMessagesResponse;\n    } catch (parseError) {\n      const err = parseError instanceof Error ? parseError : new Error(String(parseError));\n      throw new ServerClassifiedProviderError('Anthropic returned invalid JSON', {\n        kind: 'parse_error',\n        cause: err,\n      });\n    }\n\n    if (data.error) {\n      throw classifyClaudeServerError({\n        status: response.status,\n        bodyText: `${data.error.type ?? ''} ${data.error.message ?? ''}`,\n        headers: response.headers,\n        cause: new Error(`Anthropic API error: ${data.error.type} - ${data.error.message}`),\n      });\n    }\n\n    const blocks = Array.isArray(data.content) ? data.content : [];\n    const rawText = blocks\n      .filter(block => block?.type === 'text' && typeof block.text === 'string')\n      .map(block => block.text!)","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/server/generation/providers/ClaudeObservationProvider.ts#L80-L116","documentation":"The Anthropic provider attempted `response.json()` on the HTTP response from the Anthropic Messages API and the body was not valid JSON. The provider wraps the underlying parse failure in a ServerClassifiedProviderError with kind 'parse_error' so callers can distinguish malformed transport payloads from API-level errors. This is thrown from `generate` when the raw HTTP body cannot be deserialized into AnthropicMessagesResponse.","triggerScenarios":"Calling ClaudeObservationProvider.generate() when the Anthropic endpoint returns a non-JSON body: HTML error pages from proxies/CDNs, empty bodies on 502/504, truncated responses, or a wrong base URL pointing at a non-API server.","commonSituations":"Corporate proxies or gateways returning HTML '502 Bad Gateway' pages; Cloudflare challenge pages; hitting the wrong URL (e.g. a login page) after a misconfigured base URL; network middleboxes truncating streamed responses; Anthropic outage returning plain-text error pages.","solutions":["Log response.status and the raw text of the body (read it before calling response.json()) to see what the server actually returned","Verify the Anthropic base URL and that no proxy is intercepting requests to api.anthropic.com","Check the Anthropic status page for outages and retry with backoff for 5xx responses","Catch ServerClassifiedProviderError with kind 'parse_error' and surface a clear message to the user rather than crashing"],"exampleFix":"// before: json() throws on HTML proxy response\ndata = (await response.json()) as AnthropicMessagesResponse;\n\n// after: check status and read text first\nconst raw = await response.text();\nif (!response.ok) throw new ServerClassifiedProviderError(`HTTP ${response.status}: ${raw.slice(0, 200)}`, { kind: 'api_error' });\ndata = JSON.parse(raw) as AnthropicMessagesResponse;","handlingStrategy":"try-catch","validationCode":"if (!process.env.ANTHROPIC_API_KEY) throw new Error('ANTHROPIC_API_KEY is not set');","typeGuard":"function isAnthropicMessagesResponse(v: unknown): v is AnthropicMessagesResponse {\n  return typeof v === 'object' && v !== null &&\n    (Array.isArray((v as any).content) || (v as any).error !== undefined);\n}","tryCatchPattern":"try {\n  const result = await provider.generate(input);\n} catch (e) {\n  if (e instanceof ServerClassifiedProviderError && e.kind === 'parse_error') {\n    log.warn('Anthropic returned non-JSON; retrying or falling back', { cause: e.cause });\n    return fallbackProvider.generate(input);\n  }\n  throw e;\n}","preventionTips":["Read the raw body text and check response.status before parsing","Configure proxy/gateway to pass api.anthropic.com traffic through unchanged","Monitor the Anthropic status page and add backoff retry for 5xx","Keep a fallback provider for parse failures"],"tags":["json-parse","anthropic","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"}