{"record":{"id":"d20595b9abf577c5","repo":"can1357/oh-my-pi","slug":"anthropic-cache-refresh-returned-a-malformed-respo","errorCode":null,"errorMessage":"Anthropic cache refresh returned a malformed response","messagePattern":"Anthropic cache refresh returned a malformed response","errorType":"exception","errorClass":"AnthropicStreamEnvelopeError","httpStatus":null,"severity":"error","filePath":"packages/ai/src/providers/anthropic.ts","lineNumber":2059,"sourceCode":"\t\t\t\tconst { requestSignal } = activeAbortTracker;\n\t\t\t\tconst requestOptions = {\n\t\t\t\t\t...createSdkStreamRequestOptions(requestSignal, requestTimeoutMs),\n\t\t\t\t\tmaxRetries: 0,\n\t\t\t\t};\n\t\t\t\tconst request: unknown =\n\t\t\t\t\tisOAuthToken && client.beta\n\t\t\t\t\t\t? client.beta.messages.create(refreshParams, requestOptions)\n\t\t\t\t\t\t: client.messages.create(refreshParams, requestOptions);\n\t\t\t\tif (!hasAnthropicRawResponseRequest(request)) {\n\t\t\t\t\tthrow new AIError.AnthropicStreamEnvelopeError(\n\t\t\t\t\t\t\"Anthropic cache refresh request did not expose a raw response\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tconst response = await request.asResponse();\n\t\t\t\tawait notifyProviderResponse(options, response, model, response.headers.get(\"request-id\"));\n\t\t\t\tconst body: unknown = await response.json();\n\t\t\t\tif (!isRecord(body)) {\n\t\t\t\t\tthrow new AIError.AnthropicStreamEnvelopeError(\"Anthropic cache refresh returned a malformed response\");\n\t\t\t\t}\n\t\t\t\tconst wireUsage = parseAnthropicWireUsage(body.usage);\n\t\t\t\tif (!wireUsage) {\n\t\t\t\t\tthrow new AIError.AnthropicStreamEnvelopeError(\"Anthropic cache refresh response omitted usage\");\n\t\t\t\t}\n\t\t\t\tif (typeof body.id === \"string\") output.responseId = body.id;\n\t\t\t\toutput.usage.input = wireUsage.input_tokens ?? 0;\n\t\t\t\toutput.usage.output = wireUsage.output_tokens ?? 0;\n\t\t\t\toutput.usage.cacheRead = wireUsage.cache_read_input_tokens ?? 0;\n\t\t\t\toutput.usage.cacheWrite = wireUsage.cache_creation_input_tokens ?? 0;\n\t\t\t\tapplyAnthropicUsageExtras(output.usage, wireUsage);\n\t\t\t\toutput.usage.totalTokens =\n\t\t\t\t\toutput.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;\n\t\t\t\tcalculateCost(model, output.usage);\n\t\t\t\toutput.duration = performance.now() - startTime;\n\t\t\t\tstream.push({ type: \"start\", partial: output });\n\t\t\t\tstream.push({ type: \"done\", reason: \"stop\", message: output });\n\t\t\t\tstream.end();","sourceCodeStart":2041,"sourceCodeEnd":2077,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/ai/src/providers/anthropic.ts#L2041-L2077","documentation":"This AnthropicStreamEnvelopeError is thrown when the cache-refresh response body parses to something that is not a JSON object (a record). The library expects the standard Anthropic message shape and needs object access to extract usage and response id; anything else (null, array, string, HTML error page) is treated as malformed.","triggerScenarios":"request.asResponse() succeeds but response.json() yields a non-object — e.g. a gateway/proxy returning an HTML error page with 200, an empty body, or a JSON array/string.","commonSituations":"Corporate proxies or API gateways intercepting the request and returning login/error pages; misconfigured base URL pointing at a non-Anthropic endpoint; CDN error responses; mock returning invalid payloads.","solutions":["Verify the base URL and network path point at the real Anthropic API (or a correct gateway).","Log/inspect the raw response body and status to see what the endpoint actually returned.","Check proxy/gateway configuration for interception (auth redirects, HTML error pages).","Fix test mocks to return a valid message-shaped JSON object including usage."],"exampleFix":"// before\nasResponse: async () => new Response(\"<html>Service Unavailable</html>\")\n\n// after\nasResponse: async () => new Response(JSON.stringify({\n  id: \"msg_1\", type: \"message\", role: \"assistant\",\n  usage: { input_tokens: 10, output_tokens: 5 },\n}), { headers: { \"content-type\": \"application/json\" } })","handlingStrategy":"validation","validationCode":"const body: unknown = await response.json();\nif (typeof body !== \"object\" || body === null || Array.isArray(body)) {\n  throw new Error(`Cache refresh returned non-object JSON: ${typeof body}; verify the endpoint/base URL.`);\n}","typeGuard":"function isRecord(value: unknown): value is Record<string, unknown> {\n  return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}","tryCatchPattern":"try {\n  return await refreshCacheUsage(params);\n} catch (err) {\n  if (err instanceof AIError.AnthropicStreamEnvelopeError && /malformed response/.test(err.message)) {\n    logger.warn(\"Cache refresh got malformed body; keeping previous usage\", { status: err });\n    return lastKnownUsage;\n  } else {\n    throw err;\n  }\n}","preventionTips":["Verify ANTHROPIC_BASE_URL points at the real API; capture raw bodies on unexpected responses.","Bypass or correctly configure corporate proxies that inject HTML error pages.","Log response content-type and first bytes when a 200 response is not application/json.","Mock refresh responses with realistic message-shaped JSON in tests."],"tags":["anthropic","cache-refresh","malformed-response","json"],"backgroundTag":"malformed-json-response","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}