{"record":{"id":"2f4be348693a3239","repo":"thedotmack/claude-mem","slug":"parse-error-2f4be3","errorCode":"parse_error","errorMessage":"Gemini returned invalid JSON","messagePattern":"Gemini returned invalid JSON","errorType":"exception","errorClass":"ServerClassifiedProviderError","httpStatus":null,"severity":"error","filePath":"src/server/generation/providers/GeminiObservationProvider.ts","lineNumber":188,"sourceCode":"      });\n    }\n\n    if (!response.ok) {\n      const bodyText = await safeReadBody(response);\n      throw classifyGeminiServerError({\n        status: response.status,\n        bodyText,\n        headers: response.headers,\n        cause: new Error(`Gemini HTTP error (status ${response.status})`),\n      });\n    }\n\n    let data: GeminiResponse;\n    try {\n      data = (await response.json()) as GeminiResponse;\n    } catch (parseError) {\n      const err = parseError instanceof Error ? parseError : new Error(String(parseError));\n      throw new ServerClassifiedProviderError('Gemini returned invalid JSON', {\n        kind: 'parse_error',\n        cause: err,\n      });\n    }\n\n    if (data.error) {\n      throw classifyGeminiServerError({\n        status: response.status,\n        bodyText: `${data.error.status ?? ''} ${data.error.message ?? ''}`,\n        headers: response.headers,\n        cause: new Error(`Gemini HTTP error (status ${response.status})`),\n      });\n    }\n\n    const rawText = data.candidates?.[0]?.content?.parts?.[0]?.text?.trim() ?? '';\n    if (!rawText) {\n      logger.warn('SDK', 'Gemini returned empty content', { provider: 'gemini', model: this.model });\n    }","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d768ba364302d12b76e69e4f021f0bb1d2d50ed6/src/server/generation/providers/GeminiObservationProvider.ts#L170-L206","documentation":"Thrown by GeminiObservationProvider.generate() when response.json() rejects after a successful HTTP status. Classified as parse_error, it means the Gemini endpoint returned a non-JSON body (HTML, empty, or truncated). The underlying parse error is attached as cause.","triggerScenarios":"A proxy/WAF returns HTML in place of the JSON response; the Gemini endpoint returns 200 with an empty body; a gateway truncates the stream; the configured base URL points at the wrong host; a transient Google status page is served.","commonSituations":"Corporate proxy interception. Google serves an interim HTML page during an outage. A wrong base URL hits a CDN returning HTML. Network instability truncates the response.","solutions":["Inspect the cause and the status/bodyText captured just before parsing — a 2xx with non-JSON points at interception.","Retry once; transient interception often clears.","Bypass or allowlist the proxy for the Gemini host.","Verify the base URL resolves to the real Gemini API.","Capture the raw body to confirm HTML/empty, then escalate."],"exampleFix":"// before: parse without inspecting body shape\n// after: guard non-JSON before parsing\nconst bodyText = await response.text();\nif (!bodyText.trim().startsWith('{')) {\n  throw new Error('Gemini returned non-JSON body');\n}\nconst data = JSON.parse(bodyText) as GeminiResponse;","handlingStrategy":"retry","validationCode":"async function fetchGeminiJsonOrThrow(response: Response): Promise<unknown> {\n  const bodyText = await response.text();\n  if (!response.ok) {\n    throw new Error(`Gemini HTTP error (status ${response.status})`);\n  }\n  if (!bodyText || !bodyText.trim().startsWith('{')) {\n    throw new Error('Gemini returned a non-JSON body (possible proxy interception)');\n  }\n  return JSON.parse(bodyText);\n}","typeGuard":"function isGeminiResponse(v: unknown): v is GeminiResponse {\n  return typeof v === 'object' && v !== null\n    && 'candidates' in v;\n}","tryCatchPattern":"for (let attempt = 0; attempt < 3; attempt++) {\n  try {\n    return await provider.generate(context);\n  } catch (error) {\n    const isParse = error instanceof ServerClassifiedProviderError && error.kind === 'parse_error';\n    if (!isParse || attempt === 2) throw error;\n    await new Promise(r => setTimeout(r, 2 ** attempt * 500));\n  }\n}","preventionTips":["Inspect the raw body prefix before parsing to detect HTML/empty responses.","Bypass or allowlist proxies for the Gemini host.","Confirm the base URL targets the real Gemini API.","Retry parse errors with backoff since interception is often transient."],"tags":["parse","gemini","provider","network","proxy"],"backgroundTag":null,"analyzedSha":"d768ba364302d12b76e69e4f021f0bb1d2d50ed6","analyzedAt":"2026-08-12T23:52:55.241Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}