Hmbown/CodeWhale · error · RuntimeApiError
Runtime thread response is not an event stream
Error message
Runtime thread response is not an event stream
What it means
After issuing the GET events request, threadEvents() verifies the response is actually a text/event-stream with a readable body. If the content-type is not SSE (or the body is missing), it cancels the body and throws RuntimeApiError. This typically means an intermediary or the server responded with a regular HTTP response (HTML error page, JSON error) instead of the event stream the SDK requires.
Solutions
- Verify the base URL points at the Codewhale runtime API and the runtime supports thread_event_stream.
- Check the runtime version; upgrade to one that implements the thread event SSE endpoint.
- Inspect response handling in your proxy/auth layer so SSE responses pass through unmodified.
- Catch RuntimeApiError and log body/status from the error options to see what was actually returned.
Example fix
// before
const events = client.threadEvents(id);
// after
try {
for await (const ev of client.threadEvents(id)) handle(ev);
} catch (err) {
if (err instanceof RuntimeApiError) console.error(err.message, err.status, err.body);
throw err;
} Defensive patterns
Strategy: try-catch
Try / catch
try { for await (const ev of client.threadEvents(id)) handle(ev); } catch (err) { if (err instanceof RuntimeApiError && err.message.includes("not an event stream")) { log.error("non-SSE response from runtime", err.status, err.body); } throw err; } Prevention
- Smoke-test the runtime's /v1/threads/:id/events endpoint returns content-type text/event-stream before wiring consumers.
- Configure proxies to pass SSE responses through without buffering or HTML error interception.
- Pin SDK and runtime versions together and verify capability support on deploy.
When it happens
Trigger: Calling threadEvents() against a runtime that does not implement the thread_event_stream capability and returns a 404/JSON body; a proxy or auth gateway intercepting the request and returning an HTML error page; misconfigured base URL pointing at a non-runtime endpoint.
Common situations: Reverse proxy returning an error/login page; older runtime version without SSE support on /v1/threads/:id/events; wrong port or path prefix in baseUrl.
Related errors
- ${compactRuntimeError(response.status, body)}
- ${compactRuntimeError(response.status, body)}
- ${compactRuntimeError(response.status, body)}
- MCP SSE connect cancelled before the request completed
- Runtime API event response did not include a readable body
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/101c1844cf2c6f6e.
Report an issue: GitHub.
Appendix: source
Thrown at npm/runtime-sdk/index.js:145
/** 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")
throw new TypeError("includeProgress must be a boolean");
if (options.includeProgress) query.set("progress", "true");
const path = `/v1/threads/${segment(threadId)}/events?${query}`;
const response = await this.#rawRequest(path, {
method: "GET", capability: "thread_event_stream", accept: "text/event-stream",
signal: options.signal, redirect: "error",
});
if (!response.body || !/^text\/event-stream(?:;|$)/i.test(response.headers.get("content-type") ?? "")) {
await response.body?.cancel();
throw new RuntimeApiError("Runtime thread response is not an event stream", { method: "GET", path });
}
if (options.includeProgress && response.headers.get("x-codewhale-event-progress") !== "1") {
await response.body.cancel();
throw new RuntimeCapabilityError("thread_event_progress", "Runtime does not support thread replay progress", { method: "GET", path, status: 501 });
}
yield* parseEventStream(response.body, { maxFrameChars: 2 * 1024 * 1024, requireBoundary: true });
}
async #jsonRequest(path, options = {}) {
const response = await this.#rawRequest(path, options);
if (response.status === 204) {
return null;
}
return response.json();
}
async #rawRequest(path, options = {}) {
const method = options.method ?? "GET";View on GitHub (pinned to 433685b202)