can1357/oh-my-pi · error · AIError.ProviderResponseError

OpenAI stream response has no body (status ${response.status

Error message

OpenAI stream response has no body (status ${response.status})

What it means

AIError.ProviderResponseError thrown by postOpenAIStream when the OpenAI-compatible endpoint answered with an OK status but response.body is null/undefined, so SSE parsing cannot begin. This normally only happens with non-streaming-capable fetch implementations or interception layers that replace the body. It is thrown with kind "envelope" since the HTTP envelope itself is defective.

Source

Thrown at packages/ai/src/utils/openai-http.ts:111

		headers: { "Content-Type": "application/json", Accept: "text/event-stream", ...init.headers },
		body: JSON.stringify(init.body),
		signal: init.signal,
		fetch: init.fetch,
		maxAttempts: DEFAULT_MAX_ATTEMPTS,
		// A proxy concurrency-admission 429 (`rate_limit_type: max_parallel_requests`)
		// surfaces immediately instead of being slept-and-retried here; session
		// recovery owns its backoff/fallback (issue #8854).
		shouldRetryResponse: (response, bodyText) => !isConcurrencyAdmissionRejection(response, bodyText),
		// Bun's native fetch enforces a hard ~300s pre-response timeout (issue #2422).
		// Cold large-context streams legitimately exceed it; the caller's
		// `firstEventTimeoutMs`/`AbortSignal` already govern stuck requests.
		timeout: false,
	});
	if (!response.ok) {
		throw await captureOpenAIHttpError(response);
	}
	if (!response.body) {
		throw new AIError.ProviderResponseError(`OpenAI stream response has no body (status ${response.status})`, {
			kind: "envelope",
		});
	}
	return {
		events: readSseJson<TEvent>(response.body, init.signal, init.onSseEvent),
		response,
		requestId: response.headers.get("x-request-id"),
	};
}

/** Decode a non-2xx response into an {@link OpenAIHttpError} without consuming it twice. */
export async function captureOpenAIHttpError(response: Response): Promise<AIError.OpenAIHttpError> {
	let bodyText: string | undefined;
	let bodyJson: unknown;
	try {
		bodyText = await response.text();
		if (bodyText.trim().length > 0) {
			try {

View on GitHub (pinned to 9690622007)

Solutions

  1. Inspect any custom fetch implementation passed to the client — ensure it returns a Response with a live readable body.
  2. Fix test mocks to construct `new Response(readableStream)` instead of a body-less Response.
  3. Remove caching/logging wrappers that consume response.body before returning it.
  4. If the server legitimately sends an empty 200 (shouldn't for streams), check the endpoint actually supports `stream: true`.

Example fix

// before: mock strips body
fetch: async () => new Response(null, { status: 200 });
// after: mock provides a readable SSE body
fetch: async () => new Response(sseStreamOf(chunk), { status: 200, headers: { "content-type": "text/event-stream" } });
Defensive patterns

Strategy: validation

Validate before calling

// when injecting a custom fetch (tests/proxies), assert body presence before use
const res = await fetch(url, init);
if (res.ok && !res.body) throw new Error("custom fetch returned 200 without a body");

Type guard

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

Try / catch

try {
	const stream = await client.stream(request);
} catch (err) {
	if (err instanceof AIError.ProviderResponseError && /no body/.test(err.message)) {
		// fix custom fetch shim / retry with default fetch
	} else throw err;
}

Prevention

When it happens

Trigger: A successful (response.ok) streaming POST where response.body is falsy — custom ctx.fetch shims that return a Response without a body, some polyfills/test mocks, or node-fetch-style environments where body was already consumed or not provided.

Common situations: Unit tests injecting mock Responses built without a body; custom fetch wrappers (logging/caching) that read and fail to re-attach the body; runtime polyfill mismatches (e.g. undici mock agents returning opaque responses).

Related errors


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