can1357/oh-my-pi · error
JSONL stream ended unexpectedly
Error message
JSONL stream ended unexpectedly
What it means
readJsonl buffers the stream and parses newline-delimited JSON incrementally. After the stream ends, any remaining buffered tail is flushed through Bun.JSONL.parseChunk; if the tail is non-empty but parseChunk reports not-done (an incomplete final record — e.g. a line cut off mid-JSON), the stream ended in the middle of a record and this error is thrown.
Source
Thrown at packages/utils/src/stream.ts:48
export async function* readJsonl<T>(stream: ReadableStream<Uint8Array>, signal?: AbortSignal): AsyncGenerator<T> {
const buffer = new ConcatSink();
const source = abortableSource(stream, signal);
try {
for await (const chunk of source) {
yield* buffer.pullJSONL<T>(chunk, 0, chunk.length);
}
if (!buffer.isEmpty) {
const tail = buffer.flush();
if (tail) {
buffer.clear();
const { values, error, done } = Bun.JSONL.parseChunk(tail, 0, tail.length);
if (values.length > 0) {
yield* values as T[];
}
if (error) throw error;
if (!done) {
throw new Error("JSONL stream ended unexpectedly");
}
}
}
} catch (err) {
// Abort errors are expected — just stop the generator.
if (signal?.aborted) return;
throw err;
}
}
// =============================================================================
// SSE (Server-Sent Events)
// =============================================================================
class ConcatSink {
#space?: Buffer;
#length = 0;
View on GitHub (pinned to 9690622007)
Solutions
- Fix the producer to terminate each JSON record with a newline before closing the stream.
- Check for network/proxy truncation (timeouts, content-length mismatches) upstream.
- Use the signal parameter to abort cleanly, which is swallowed instead of thrown.
- Catch the error and treat already-yielded values as a partial (best-effort) result.
Example fix
// before
for await (const v of readJsonl(stream)) use(v);
// after
let partial = [];
try {
for await (const v of readJsonl(stream, signal)) use(v);
} catch (err) {
flushPartialResults(partial, err);
} Defensive patterns
Strategy: try-catch
Try / catch
const results = [];
try {
for await (const v of readJsonl<T>(stream, signal)) results.push(v);
} catch (err) {
if (String(err.message).includes('JSONL stream ended unexpectedly')) {
logger.warn('jsonl truncated; using partial results', { count: results.length });
} else throw err;
} Prevention
- Ensure producers newline-terminate every JSON record before closing.
- Always pass an AbortSignal so disconnects are treated as clean stops.
- Monitor upstream proxies/CDNs for body truncation on long streams.
When it happens
Trigger: A JSONL producer closes the stream after a partial final line — truncated HTTP body, process killed mid-write, network drop without abort, or a server that doesn't end with a newline and sends malformed trailing bytes.
Common situations: Streaming Ollama/LLM responses where the connection drops mid-token; reading logs from a crashed process; premature response termination from proxies with body size limits.
Related errors
- Request was aborted
- No response body for V2 compaction streaming
- V2 compaction stream closed before response.completed
- V2 compaction stream parse failed: ${err instanceof Error ?
- formatCompactionV2Failure(event, type)
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/572423e0519c2ab1.
Report an issue: GitHub.