{"record":{"id":"288188cedafdd59b","repo":"can1357/oh-my-pi","slug":"gemini-files-api-context-response-is-not-valid","errorCode":null,"errorMessage":"Gemini Files API ${context} response is not valid JSON","messagePattern":"Gemini Files API (.+?) response is not valid JSON","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/provider-files-gemini.ts","lineNumber":28,"sourceCode":"\tname: string;\n\turi: string;\n\tmimeType: string;\n\texpiresAt: number;\n}\n\nfunction responseObject(value: unknown, context: string): Record<string, unknown> {\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n\t\tthrow new Error(`Gemini Files API ${context} response is not a JSON object`);\n\t}\n\treturn value as Record<string, unknown>;\n}\n\nasync function responseJson(response: Response, context: string): Promise<Record<string, unknown>> {\n\ttry {\n\t\treturn responseObject((await response.json()) as unknown, context);\n\t} catch (error) {\n\t\tif (error instanceof Error && error.message.startsWith(\"Gemini Files API\")) throw error;\n\t\tthrow new Error(`Gemini Files API ${context} response is not valid JSON`);\n\t}\n}\n\nfunction requireString(value: unknown, field: string): string {\n\tif (typeof value !== \"string\" || value.length === 0) {\n\t\tthrow new Error(`Gemini Files API finalize response is missing ${field}`);\n\t}\n\treturn value;\n}\n\nfunction parseFinalizedFile(payload: Record<string, unknown>): GeminiFileResource {\n\tconst file = responseObject(payload.file, \"finalize file\");\n\tconst state = requireString(file.state, \"file.state\");\n\tif (state !== \"ACTIVE\") throw new Error(\"Gemini Files API finalized file state is not ACTIVE\");\n\n\tconst name = requireString(file.name, \"file.name\");\n\tif (!/^files\\/[^/]+$/.test(name)) {\n\t\tthrow new Error(\"Gemini Files API finalize response contains an invalid file.name\");","sourceCodeStart":10,"sourceCodeEnd":46,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/provider-files-gemini.ts#L10-L46","documentation":"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').","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: assuming every 2xx body is JSON\nconst data = await finalizeResponse.json();\n// after: tolerate transient bad bodies with a retry\nlet data;\nfor (let attempt = 0; attempt < 3; attempt++) {\n  try { data = await client.upload(request); break; }\n  catch (e) {\n    if (attempt === 2 || !String(e.message).includes(\"not valid JSON\")) throw e;\n    await Bun.sleep(1000);\n  }\n}","handlingStrategy":"retry","validationCode":"null","typeGuard":"function looksLikeGeminiJson(text: string): boolean {\n  try { const v = JSON.parse(text); return v !== null && typeof v === \"object\" && !Array.isArray(v); }\n  catch { return false; }\n}","tryCatchPattern":"try {\n  const handle = await client.upload(request);\n} catch (error) {\n  if (error instanceof Error && error.message.includes(\"is not valid JSON\")) {\n    // transient bad body: retry, or inspect raw traffic via a wrapper fetchImpl\n  } else throw error;\n}","preventionTips":["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"],"tags":["network","json","api-response","parsing"],"backgroundTag":"invalid-json-response","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}