Hmbown/CodeWhale · error · RuntimeApiError

Runtime API event response did not include a readable body

Error message

Runtime API event response did not include a readable body

What it means

fleetEvents yields events from the runtime API's event stream. When the API has no buffered events it expects to consume an SSE stream from response.body; if the response has no readable body it throws RuntimeApiError, since streaming is impossible.

Solutions

  1. If using a fetch mock, provide a body (e.g. a ReadableStream or an SSE-shaped Response).
  2. Check whether the server should actually have events; an empty/compressed response may indicate a server bug.
  3. Use a runtime/polyfill that supports response.body as a readable stream.

Example fix

// before
mockFetch = async () => new Response(null, { status: 200 });
// after
mockFetch = async () => new Response("data: {\"type\":\"run_started\"}\n\n", { status: 200, headers: { "content-type": "text/event-stream" } });
Defensive patterns

Strategy: try-catch

Validate before calling

const res = await fetch(url); if (!res.body || typeof res.body.getReader !== "function") throw new Error("Response has no readable stream body");

Type guard

const hasReadableBody = (res) => !!res.body && typeof res.body.getReader === "function";

Try / catch

try { for await (const ev of client.fleetEvents(runId)) handle(ev); } catch (e) { if (e instanceof RuntimeApiError && e.message.includes("readable body")) { console.error("Check fetch mock/server response for a missing body stream"); } else throw e; }

Prevention

When it happens

Trigger: A GET to the events endpoint returns a response whose body is null or non-readable — typical with mock/stub HTTP clients or unusual proxies — while the request expected a streaming response.

Common situations: Testing with a fetch mock that returns { ok: true } without a body; an intermediary returning an empty 204-style response; a fetch polyfill that doesn't expose body streams.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/0c677e62c5b5f96c. Report an issue: GitHub.

Appendix: source

Thrown at npm/runtime-sdk/index.js:117

      options.path ?? `/v1/fleet/runs/${segment(runId)}/events`,
      options,
    );
    const response = await this.#rawRequest(path, {
      method: "GET",
      capability: "fleet_event_stream",
      accept: "text/event-stream",
    });
    const contentType = response.headers.get("content-type") ?? "";
    if (contentType.includes("application/json")) {
      const payload = await response.json();
      const events = Array.isArray(payload) ? payload : (payload.events ?? []);
      for (const event of events) {
        yield event;
      }
      return;
    }
    if (!response.body) {
      throw new RuntimeApiError("Runtime API event response did not include a readable body", {
        method: "GET",
        path,
      });
    }
    for await (const event of parseEventStream(response.body)) {
      yield event;
    }
  }

  /** Read the existing durable thread journal. This never starts a turn. */
  async *threadEvents(threadId, options = {}) {
    const query = new URLSearchParams();
    for (const [key, value] of [["since_seq", options.sinceSeq], ["replay_limit", options.replayLimit]]) {
      if (value === undefined) continue;
      if (!Number.isSafeInteger(value) || value < 0) throw new TypeError(`${key} must be a nonnegative safe integer`);
      query.set(key, String(value));
    }
    if (options.includeProgress !== undefined && typeof options.includeProgress !== "boolean")

View on GitHub (pinned to 433685b202)