{"record":{"id":"663ae42bc220367b","repo":"can1357/oh-my-pi","slug":"gemini-api-returned-no-response-body","errorCode":null,"errorMessage":"Gemini API returned no response body","messagePattern":"Gemini API returned no response body","errorType":"http","errorClass":"SearchProviderError","httpStatus":500,"severity":"error","filePath":"packages/coding-agent/src/web/search/providers/gemini.ts","lineNumber":529,"sourceCode":"\t\t\tbreak;\n\t\t} catch (error) {\n\t\t\tif (isLastEndpoint) {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!response?.ok) {\n\t\tconst rawErrorText = response ? await response.text() : \"Network error\";\n\t\tconst errorText = auth.accessToken ? rawErrorText.split(auth.accessToken).join(\"[redacted]\") : rawErrorText;\n\t\tconst status = response?.status ?? 502;\n\t\tconst classified = classifyProviderHttpError(\"gemini\", status, errorText);\n\t\tif (classified) throw classified;\n\t\tthrow new SearchProviderError(\"gemini\", `Gemini Cloud Code API error (${status}): ${errorText}`, status);\n\t}\n\n\tif (!response.body) {\n\t\tthrow new SearchProviderError(\"gemini\", \"Gemini API returned no response body\", 500);\n\t}\n\n\treturn finalizeGeminiSearchResult(await parseGeminiSearchStream(response.body, model), fetchImpl, signal);\n}\n\nasync function callGeminiDeveloperSearch(\n\tapiKey: string,\n\tendpoint: GeminiDeveloperEndpoint,\n\tmodel: string,\n\tquery: string,\n\tsystemPrompt: string | undefined,\n\tmaxOutputTokens: number | undefined,\n\ttemperature: number | undefined,\n\ttoolParams: GeminiToolParams,\n\tfetchImpl: FetchImpl | undefined,\n\tsignal: AbortSignal | undefined,\n\ttimeoutMs: number | undefined,\n): Promise<GeminiSearchResult> {","sourceCodeStart":511,"sourceCodeEnd":547,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/web/search/providers/gemini.ts#L511-L547","documentation":"After callGeminiSearch() gets an ok HTTP response from the Gemini Cloud Code API, it verifies a response body exists before attempting to parse the search stream. A null/empty body on an otherwise-ok response is unexpected and thrown as a 500 SearchProviderError, since grounded results are streamed from the body.","triggerScenarios":"The Gemini endpoint returned a success status (2xx) but `response.body` is null — typically only possible with non-streaming/mock fetch implementations, HEAD-like responses, or unusual proxies that consume the body.","commonSituations":"Custom fetch implementations (tests, proxies) that return ok responses without bodies, middlewares that drain the stream, or HTTP/2 edge cases where the body was not delivered.","solutions":["Check any custom fetch implementation passed to the search call — ensure it returns a real ReadableStream body for ok responses","Inspect proxies/middlewares between you and Gemini that may consume or drop the response body","Retry the request; if reproducible, log the full response to confirm status vs. body mismatch","Fall back to a direct connection (bypass proxy) to rule out infrastructure stripping the body"],"exampleFix":"// before\nconst response = await customFetch(url, init); // returns new Response(null, {status:200})\n// after\nconst response = await customFetch(url, init); // return new Response(JSON.stringify(payload), {status:200})","handlingStrategy":"try-catch","validationCode":"// When injecting a custom fetch (tests/proxies), assert it yields a body on 2xx:\nfunction assertFetchYieldsBody(fetchImpl: typeof fetch) {\n  return async (url: string, init?: RequestInit) => {\n    const res = await fetchImpl(url, init);\n    if (res.ok && !res.body) throw new Error(\"Custom fetch returned ok response with no body\");\n    return res;\n  };\n}","typeGuard":"function hasBody(res: Response | null): res is Response & { body: ReadableStream<Uint8Array> } {\n  return res !== null && res.body !== null;\n}","tryCatchPattern":"try {\n  results = await searchGemini(query);\n} catch (err) {\n  if (err instanceof SearchProviderError && err.message === \"Gemini API returned no response body\") {\n    logger.error(\"Gemini ok response lacked a body — check custom fetch/proxy\", { cause: err });\n    results = await fallbackSearch(query);\n  } else throw err;\n}","preventionTips":["Ensure custom/mock fetch implementations return a real ReadableStream body for ok responses","Audit proxies/middleware for body-consuming behavior (e.g. logging interceptors that drain the stream)","Bypass the proxy once to confirm infrastructure vs. provider cause","Retry once on this error before surfacing — it is usually infra-side, not query-side"],"tags":["network","response-parsing","gemini","empty-response"],"backgroundTag":"empty-response-body","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}