can1357/oh-my-pi · error · AnthropicStreamEnvelopeError

Attempted to iterate over an Anthropic response with no body

Error message

Attempted to iterate over an Anthropic response with no body

What it means

This AnthropicStreamEnvelopeError is thrown when the code tries to consume a streaming response whose body is null. SSE streaming requires a readable body; a null body means there is no stream to parse, so the library fails immediately rather than yielding zero events silently.

Source

Thrown at packages/ai/src/providers/anthropic.ts:1430

		if (message) {
			return new AIError.ProviderResponseError(
				errorType ? `Anthropic stream error (${errorType}): ${message}` : `Anthropic stream error: ${message}`,
				{ provider: "anthropic", kind: "output" },
			);
		}
	} catch {
		// Not a JSON envelope; fall through to the raw payload.
	}
	return new AIError.ProviderResponseError(data, { provider: "anthropic", kind: "output" });
}

async function* iterateAnthropicEvents(
	response: Response,
	signal?: AbortSignal,
	onSseEvent?: AnthropicOptions["onSseEvent"],
): AsyncGenerator<AnthropicStreamEvent> {
	if (!response.body) {
		throw new AIError.AnthropicStreamEnvelopeError("Attempted to iterate over an Anthropic response with no body");
	}

	let sawMessageStart = false;
	let sawMessageEnd = false;

	for await (const sse of readSseEvents(response.body, signal)) {
		notifyRawSseEvent(onSseEvent, sse);
		if (sse.event === "error") {
			throw createAnthropicSseStreamError(sse.data);
		}

		if (sse.event === "ping") {
			// Surface keepalives so the idle watchdog treats them as liveness.
			yield ANTHROPIC_PING_EVENT;
			continue;
		}

		if (!ANTHROPIC_MESSAGE_EVENTS.has(sse.event ?? "")) {

View on GitHub (pinned to 9690622007)

Solutions

  1. Ensure the fetch/API call used streaming semantics so the response carries a readable body.
  2. If mocking, supply a ReadableStream body (e.g. a Response with an SSE text stream).
  3. Audit custom fetch wrappers/proxies for body consumption or removal before the Response is returned.
  4. Check for earlier code that called response.json()/text() on the same Response, which locks/detaches the body.

Example fix

// before
const res = new Response(); // body: null
yield* iterateAnthropicEvents(res);

// after
const sse = 'event: message_start\ndata: {"type":"message_start"}\n\n';
const res = new Response(sse, { headers: { "content-type": "text/event-stream" } });
yield* iterateAnthropicEvents(res);
Defensive patterns

Strategy: type-guard

Validate before calling

if (!response.body) {
  throw new Error("Streaming response has no body; check that the request used streaming and the fetch wrapper preserves the body.");
}

Type guard

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

Try / catch

try {
  for await (const event of iterateAnthropicEvents(response, signal)) {
    handle(event);
  }
} catch (err) {
  if (err instanceof AIError.AnthropicStreamEnvelopeError && /no body/.test(err.message)) {
    // fall back to a fresh non-streaming request or re-issue the streaming call
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Passing a Response object with a null body into iterateAnthropicEvents — e.g. a response constructed manually, a mock, a 204/bodiless response, or a runtime that already consumed or detached the body.

Common situations: Test mocks returning `new Response()` with no body; proxy/gateway stripping the body; calling the streaming path with a non-streaming response object; a custom fetch wrapper that reads the body before returning the Response.

Related errors


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