thedotmack/claude-mem · error · ServerClassifiedProviderError
parse_error
parse_error
Error message
Gemini returned invalid JSON
What it means
The Gemini provider could not parse the HTTP response body from the Google Generative Language API as JSON while running `generate`. The underlying SyntaxError from `response.json()` is wrapped as a ServerClassifiedProviderError with kind 'parse_error'. GeminiResponses that fail include HTML error pages, empty bodies, and malformed payloads.
Solutions
- Capture response.status and the first bytes of the raw body to identify what was returned instead of JSON
- Confirm the request reaches generativelanguage.googleapis.com without proxy interference (bypass proxy for this host if needed)
- Check the model name and API version in the request path — some invalid paths return HTML/empty bodies
- Catch the classified parse_error and implement retry-with-backoff for transient gateway failures
Example fix
// before
data = (await response.json()) as GeminiResponse;
// after: surface the real payload on parse failure
const raw = await response.text();
try {
data = JSON.parse(raw) as GeminiResponse;
} catch (e) {
throw new ServerClassifiedProviderError(`Gemini returned non-JSON (status ${response.status}): ${raw.slice(0, 200)}`, { kind: 'parse_error', cause: e as Error });
} Defensive patterns
Strategy: try-catch
Validate before calling
const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`;
new URL(url); // throws early if the composed URL is malformed Type guard
function isGeminiResponse(v: unknown): v is GeminiResponse {
return typeof v === 'object' && v !== null &&
((v as any).error !== undefined || (v as any).candidates !== undefined || (v as any).promptFeedback !== undefined);
} Try / catch
try {
const result = await provider.generate(input);
} catch (e) {
if (e instanceof ServerClassifiedProviderError && e.kind === 'parse_error') {
logger.warn('Gemini returned non-JSON body', { cause: e.cause });
await sleep(backoff());
return retryWithBackoff(() => provider.generate(input), 3);
}
throw e;
} Prevention
- Check response.ok and read raw text before JSON.parse to capture real payloads
- Bypass corporate proxies for Google API hosts or add retry for gateway HTML responses
- Use exponential backoff for transient gateway errors
- Pin model names and API versions to avoid undocumented error pages
When it happens
Trigger: Calling GeminiObservationProvider.generate() when the Gemini endpoint returns a non-JSON body: HTML from proxies or a captive portal, empty 5xx bodies, gzip/streaming truncation, or the URL resolving to something other than the API (bad base URL, blocked region interstitial).
Common situations: VPN/corporate network injecting HTML blocks; Google outage or quota page returned as HTML; misconfigured proxy stripping response bodies; wrong model path returning a non-JSON error page; intermittent truncation on large responses.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- OpenRouter returned invalid JSON
- parse_error
- API key prompt cancelled — falling back to Claude provider.
- auth_invalid
- classifyGeminiError( )
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/2f4be348693a3239.
Report an issue: GitHub.
Appendix: source
Thrown at src/server/generation/providers/GeminiObservationProvider.ts:188
});
}
if (!response.ok) {
const bodyText = await safeReadBody(response);
throw classifyGeminiServerError({
status: response.status,
bodyText,
headers: response.headers,
cause: new Error(`Gemini HTTP error (status ${response.status})`),
});
}
let data: GeminiResponse;
try {
data = (await response.json()) as GeminiResponse;
} catch (parseError) {
const err = parseError instanceof Error ? parseError : new Error(String(parseError));
throw new ServerClassifiedProviderError('Gemini returned invalid JSON', {
kind: 'parse_error',
cause: err,
});
}
if (data.error) {
throw classifyGeminiServerError({
status: response.status,
bodyText: `${data.error.status ?? ''} ${data.error.message ?? ''}`,
headers: response.headers,
cause: new Error(`Gemini HTTP error (status ${response.status})`),
});
}
const rawText = data.candidates?.[0]?.content?.parts?.[0]?.text?.trim() ?? '';
if (!rawText) {
logger.warn('SDK', 'Gemini returned empty content', { provider: 'gemini', model: this.model });
}View on GitHub (pinned to d8bc9755e7)