{"record":{"id":"b6712ff2eab2c6b6","repo":"can1357/oh-my-pi","slug":"gemini-api-returned-an-empty-grounded-response","errorCode":null,"errorMessage":"Gemini API returned an empty grounded response","messagePattern":"Gemini API returned an empty grounded response","errorType":"exception","errorClass":"SearchProviderError","httpStatus":502,"severity":"error","filePath":"packages/coding-agent/src/web/search/providers/gemini.ts","lineNumber":357,"sourceCode":"\t\t\tredirect: \"manual\",\n\t\t\tsignal: withHardTimeout(signal, 5000),\n\t\t});\n\t\tconst location = response.headers.get(\"location\");\n\t\tif (!location) return proxyUrl;\n\t\tconst resolved = new URL(location, proxyUrl);\n\t\treturn resolved.protocol === \"http:\" || resolved.protocol === \"https:\" ? resolved.toString() : proxyUrl;\n\t} catch {\n\t\treturn proxyUrl;\n\t}\n}\n\nasync function finalizeGeminiSearchResult(\n\tresult: GeminiSearchResult,\n\tfetchImpl: FetchImpl | undefined,\n\tsignal: AbortSignal | undefined,\n): Promise<GeminiSearchResult> {\n\tif (!result.answer && result.sources.length === 0) {\n\t\tthrow new SearchProviderError(\"gemini\", \"Gemini API returned an empty grounded response\", 502);\n\t}\n\n\tconst redirectUrls = new Set<string>();\n\tfor (const source of result.sources) {\n\t\tif (isGroundingRedirectUrl(source.url)) redirectUrls.add(source.url);\n\t}\n\tfor (const citation of result.citations) {\n\t\tif (isGroundingRedirectUrl(citation.url)) redirectUrls.add(citation.url);\n\t}\n\tif (redirectUrls.size === 0) return result;\n\n\tsignal?.throwIfAborted();\n\tconst resolvedEntries = await Promise.all(\n\t\t[...redirectUrls].map(async url => [url, await resolveGroundingRedirect(url, fetchImpl, signal)] as const),\n\t);\n\tsignal?.throwIfAborted();\n\tconst resolvedUrls = new Map(resolvedEntries);\n\tfor (const source of result.sources) {","sourceCodeStart":339,"sourceCodeEnd":375,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/web/search/providers/gemini.ts#L339-L375","documentation":"finalizeGeminiSearchResult() throws this after a Gemini grounded search completes when the result contains neither an answer text nor any grounding sources. Gemini is expected to return a grounded response (answer and/or grounding chunks); an entirely empty result indicates the model returned no usable grounded content, which is treated as a provider failure (HTTP 502).","triggerScenarios":"parseGeminiSearchStream produced a GeminiSearchResult with empty `answer` and zero `sources` — e.g. the model declined/refused to answer, the grounding metadata was empty in the stream, the prompt/query yielded no grounded output, or stream parsing dropped all content.","commonSituations":"Queries the model refuses or answers with empty candidates, proxy/gateway stripping grounding metadata from the response, overly restrictive safety settings, or a malformed/truncated stream that yielded no chunks.","solutions":["Retry with a rephrased, more specific query — empty grounded responses often follow prompts the model won't ground on","Inspect the raw API response (log before parsing) to see whether candidates/groundingMetadata were present upstream","Check safety settings and model configuration; an over-blocked prompt returns empty candidates","Verify any proxy/gateway between you and Gemini preserves the grounding metadata fields"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"// No pre-call validation; shape the prompt so grounding is likely:\nconst safeQuery = query.trim();\nif (!safeQuery) throw new Error(\"Refusing empty Gemini search query\");","typeGuard":"function hasGroundedContent(r: { answer?: string; sources: unknown[] }): boolean {\n  return Boolean(r.answer && r.answer.trim()) || r.sources.length > 0;\n}","tryCatchPattern":"try {\n  results = await searchGemini(query);\n} catch (err) {\n  if (err instanceof SearchProviderError && err.message === \"Gemini API returned an empty grounded response\") {\n    logger.warn(\"Gemini grounded search empty — retrying with rephrased query, then fallback\", { query });\n    results = await searchGemini(rephrase(query)).catch(() => fallbackSearch(query));\n  } else throw err;\n}","preventionTips":["Retry once with a rephrased query before surfacing the failure","Check upstream raw candidates/groundingMetadata when this recurs to distinguish refusal vs. parsing loss","Avoid over-restrictive safety settings that empty out candidates","Have a non-grounded fallback provider for queries Gemini won't ground on"],"tags":["api-provider","empty-response","gemini"],"backgroundTag":"empty-api-response","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}