Hmbown/CodeWhale · error · Error
Runtime event stream ended inside a frame
Error message
Runtime event stream ended inside a frame
What it means
With requireBoundary enabled (threadEvents uses it), the stream must end exactly on a frame boundary. If buffered text remains after the connection closes and final decoding, parseEventStream() throws, indicating a truncated or corrupt SSE stream rather than silently dropping a partial event.
Solutions
- Reconnect and resume the replay with { sinceSeq: lastProcessedSeq } to get the missing tail.
- Add retry/backoff around stream consumption for transient network drops.
- Check intermediary (proxy/LB) timeouts and raise them for long-lived SSE connections.
- Investigate runtime logs if truncation recurs — the server likely crashed mid-frame.
Example fix
// before
for await (const ev of client.threadEvents(id)) handle(ev);
// after
let since;
for (;;) {
try {
for await (const ev of client.threadEvents(id, { sinceSeq: since })) {
handle(ev); since = ev.seq;
}
break;
} catch (err) {
if (!err.message.includes("ended inside a frame")) throw err;
await sleep(500);
}
} Defensive patterns
Strategy: retry
Try / catch
let since;
while (true) {
try {
for await (const ev of client.threadEvents(id, { sinceSeq: since })) { handle(ev); since = ev.seq; }
break;
} catch (err) {
if (!err.message.includes("ended inside a frame")) throw err;
await backoff();
}
} Prevention
- Always resume replays with sinceSeq set to the last processed event so truncation is recoverable.
- Raise proxy/load-balancer idle timeouts for long-lived SSE connections.
- Monitor runtime liveness; repeated truncation usually means the server is crashing mid-frame.
When it happens
Trigger: The runtime or an intermediary closes the TCP connection mid-frame — network drop, proxy timeout, server crash, or a runtime that writes a partial event and exits. threadEvents passes requireBoundary: true.
Common situations: Load balancer idle/short timeouts killing long SSE connections; container OOM-killed mid-write; client machine sleeping during a long replay.
Related errors
- runtime event stream ended before turn.completed
- runtime SSE frame exceeded
- SSE stream idle timeout after
- SSE stream idle timeout after
- SSE stream idle timeout after
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/b5544304ecb2649c.
Report an issue: GitHub.
Appendix: source
Thrown at npm/runtime-sdk/index.js:255
async function* parseEventStream(body, { maxFrameChars = Infinity, requireBoundary = false } = {}) {
const decoder = new TextDecoder("utf-8", { fatal: requireBoundary });
let buffer = "";
for await (const chunk of body) {
buffer += decoder.decode(chunk, { stream: true });
let boundary;
while ((boundary = eventStreamBoundary(buffer)) !== null) {
if (boundary.index > maxFrameChars) throw new Error("Runtime event frame exceeds the size limit");
const frame = buffer.slice(0, boundary.index);
buffer = buffer.slice(boundary.index + boundary.length);
const event = parseSseFrame(frame);
if (event !== undefined) {
yield event;
}
}
if (buffer.length > maxFrameChars) throw new Error("Runtime event frame exceeds the size limit");
}
buffer += decoder.decode();
if (requireBoundary && buffer.trim()) throw new Error("Runtime event stream ended inside a frame");
const event = parseSseFrame(buffer);
if (event !== undefined) {
yield event;
}
}
function eventStreamBoundary(buffer) {
const lf = buffer.indexOf("\n\n");
const crlf = buffer.indexOf("\r\n\r\n");
if (lf < 0 && crlf < 0) {
return null;
}
if (crlf >= 0 && (lf < 0 || crlf < lf)) {
return { index: crlf, length: 4 };
}
return { index: lf, length: 2 };
}
View on GitHub (pinned to 433685b202)