can1357/oh-my-pi · error · SearchProviderError

Gemini API returned no response body

Error message

Gemini API returned no response body

What it means

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.

Source

Thrown at packages/coding-agent/src/web/search/providers/gemini.ts:529

			break;
		} catch (error) {
			if (isLastEndpoint) {
				throw error;
			}
		}
	}

	if (!response?.ok) {
		const rawErrorText = response ? await response.text() : "Network error";
		const errorText = auth.accessToken ? rawErrorText.split(auth.accessToken).join("[redacted]") : rawErrorText;
		const status = response?.status ?? 502;
		const classified = classifyProviderHttpError("gemini", status, errorText);
		if (classified) throw classified;
		throw new SearchProviderError("gemini", `Gemini Cloud Code API error (${status}): ${errorText}`, status);
	}

	if (!response.body) {
		throw new SearchProviderError("gemini", "Gemini API returned no response body", 500);
	}

	return finalizeGeminiSearchResult(await parseGeminiSearchStream(response.body, model), fetchImpl, signal);
}

async function callGeminiDeveloperSearch(
	apiKey: string,
	endpoint: GeminiDeveloperEndpoint,
	model: string,
	query: string,
	systemPrompt: string | undefined,
	maxOutputTokens: number | undefined,
	temperature: number | undefined,
	toolParams: GeminiToolParams,
	fetchImpl: FetchImpl | undefined,
	signal: AbortSignal | undefined,
	timeoutMs: number | undefined,
): Promise<GeminiSearchResult> {

View on GitHub (pinned to 9690622007)

Solutions

  1. Check any custom fetch implementation passed to the search call — ensure it returns a real ReadableStream body for ok responses
  2. Inspect proxies/middlewares between you and Gemini that may consume or drop the response body
  3. Retry the request; if reproducible, log the full response to confirm status vs. body mismatch
  4. Fall back to a direct connection (bypass proxy) to rule out infrastructure stripping the body

Example fix

// before
const response = await customFetch(url, init); // returns new Response(null, {status:200})
// after
const response = await customFetch(url, init); // return new Response(JSON.stringify(payload), {status:200})
Defensive patterns

Strategy: try-catch

Validate before calling

// When injecting a custom fetch (tests/proxies), assert it yields a body on 2xx:
function assertFetchYieldsBody(fetchImpl: typeof fetch) {
  return async (url: string, init?: RequestInit) => {
    const res = await fetchImpl(url, init);
    if (res.ok && !res.body) throw new Error("Custom fetch returned ok response with no body");
    return res;
  };
}

Type guard

function hasBody(res: Response | null): res is Response & { body: ReadableStream<Uint8Array> } {
  return res !== null && res.body !== null;
}

Try / catch

try {
  results = await searchGemini(query);
} catch (err) {
  if (err instanceof SearchProviderError && err.message === "Gemini API returned no response body") {
    logger.error("Gemini ok response lacked a body — check custom fetch/proxy", { cause: err });
    results = await fallbackSearch(query);
  } else throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/663ae42bc220367b. Report an issue: GitHub.