can1357/oh-my-pi · error
Gemini Files API ${context} response is not valid JSON
Error message
Gemini Files API ${context} response is not valid JSON What it means
This is thrown by responseJson() when the Gemini Files API HTTP response body cannot be parsed as JSON, or parses to a non-object (null/array). The library only surfaces Google's response payload after an HTTP 2xx, so this means the 2xx body itself was malformed (HTML error page, empty body, truncated stream, or a proxy MITM). It wraps the underlying SyntaxError with a stable message that includes the request context (e.g. 'finalize').
Source
Thrown at packages/coding-agent/src/blob-broker/provider-files-gemini.ts:28
name: string;
uri: string;
mimeType: string;
expiresAt: number;
}
function responseObject(value: unknown, context: string): Record<string, unknown> {
if (typeof value !== "object" || value === null || Array.isArray(value)) {
throw new Error(`Gemini Files API ${context} response is not a JSON object`);
}
return value as Record<string, unknown>;
}
async function responseJson(response: Response, context: string): Promise<Record<string, unknown>> {
try {
return responseObject((await response.json()) as unknown, context);
} catch (error) {
if (error instanceof Error && error.message.startsWith("Gemini Files API")) throw error;
throw new Error(`Gemini Files API ${context} response is not valid JSON`);
}
}
function requireString(value: unknown, field: string): string {
if (typeof value !== "string" || value.length === 0) {
throw new Error(`Gemini Files API finalize response is missing ${field}`);
}
return value;
}
function parseFinalizedFile(payload: Record<string, unknown>): GeminiFileResource {
const file = responseObject(payload.file, "finalize file");
const state = requireString(file.state, "file.state");
if (state !== "ACTIVE") throw new Error("Gemini Files API finalized file state is not ACTIVE");
const name = requireString(file.name, "file.name");
if (!/^files\/[^/]+$/.test(name)) {
throw new Error("Gemini Files API finalize response contains an invalid file.name");View on GitHub (pinned to 9690622007)
Solutions
- Check the raw response (curl the upload/finalize endpoint with your API key) to see what non-JSON body is actually returned.
- Rule out proxies/VPNs/captive portals intercepting generativelanguage.googleapis.com traffic.
- Retry the upload — transient truncation of the 2xx body is the most common cause.
- If using a custom FetchImpl (tests, SDK embed), verify it returns a proper JSON Response with correct Content-Type.
Example fix
// before: assuming every 2xx body is JSON
const data = await finalizeResponse.json();
// after: tolerate transient bad bodies with a retry
let data;
for (let attempt = 0; attempt < 3; attempt++) {
try { data = await client.upload(request); break; }
catch (e) {
if (attempt === 2 || !String(e.message).includes("not valid JSON")) throw e;
await Bun.sleep(1000);
}
} Defensive patterns
Strategy: retry
Validate before calling
null
Type guard
function looksLikeGeminiJson(text: string): boolean {
try { const v = JSON.parse(text); return v !== null && typeof v === "object" && !Array.isArray(v); }
catch { return false; }
} Try / catch
try {
const handle = await client.upload(request);
} catch (error) {
if (error instanceof Error && error.message.includes("is not valid JSON")) {
// transient bad body: retry, or inspect raw traffic via a wrapper fetchImpl
} else throw error;
} Prevention
- Bypass or correctly configure proxies/VPNs for generativelanguage.googleapis.com
- Wrap fetchImpl with a logger that captures response text for 2xx responses during debugging
- Retry uploads once or twice automatically — malformed 2xx bodies are usually transient
- In tests, always return real JSON Response objects from mock FetchImpls
When it happens
Trigger: parseFinalizedFile's caller invokes responseJson(finalizeResponse, "finalize") after a successful resumable-upload finalize POST; response.json() throws a SyntaxError, or responseObject throws a non-Gemini-prefixed error, causing the rethrow at provider-files-gemini.ts:28.
Common situations: Corporate proxy or captive portal returning HTML instead of JSON; API returning 200 with an empty body during partial outages; response body truncated mid-stream; a stubbed fetchImpl returning a non-JSON Response in tests.
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.
Related errors
- seafile upload-link response did not include a URL
- Replacement text is not valid UTF-8: {err}
- V2 compaction stream parse failed: ${err instanceof Error ?
- ${argv[0]} did not report a tunnel URL within ${READY_TIMEOU
- Gemini Files API finalize response is missing ${field}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/288188cedafdd59b.
Report an issue: GitHub.