janhq/jan · error
Malformed chunk
Error message
Malformed chunk
What it means
Thrown when an SSE line from the llama.cpp server starts with neither 'data: ' nor 'error: ', so the stream parser cannot classify the chunk. The comment in the source notes this should not normally happen; it guards against protocol violations.
Solutions
- Inspect the raw chunk contents (log trimmedLine) to see what the server actually sent.
- Check for proxies/load balancers altering the streaming response and bypass them.
- Verify the llama.cpp server version matches what the extension expects.
- Make the parser resilient: skip empty/comment lines (': keep-alive') instead of throwing.
Example fix
// before
} else {
throw new Error('Malformed chunk')
}
// after
} else if (trimmedLine.length > 0 && !trimmedLine.startsWith(':')) {
throw new Error(`Malformed chunk: ${trimmedLine.slice(0, 100)}`)
} Defensive patterns
Strategy: validation
Validate before calling
function isSseLine(line: string): boolean {
return line.startsWith('data: ') || line.startsWith('error: ') || line.startsWith(':') || line.length === 0
} Type guard
null
Try / catch
try {
for await (const chunk of stream) yield chunk
} catch (e) {
if ((e as Error).message === 'Malformed chunk') {
logRawStreamForDebugging()
throw new Error('Incompatible llamacpp server or interfering proxy on SSE stream')
}
throw e
} Prevention
- Bypass reverse proxies/load balancers when streaming, or configure them to pass SSE through unbuffered.
- Keep the llama.cpp server and extension versions aligned.
- Test the SSE endpoint directly with curl to compare raw output.
- Log the offending line before throwing to ease diagnosis.
When it happens
Trigger: The server emits an unexpected line in the SSE stream — non-standard keep-alive comments not filtered out, a proxy injecting HTML error pages into the stream, or a llama.cpp version changing its stream format.
Common situations: Reverse proxy or middleware intercepting the streaming response; mismatched server/extension versions with different SSE framing; corrupted stream due to network truncation combined with buffer mis-splitting.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
AI-assisted analysis of janhq/jan@7205d770c1 (2026-09-17).
Data as JSON: /api/errors/8b5edfe9f25bde4e.
Report an issue: GitHub.
Appendix: source
Thrown at extensions/llamacpp-extension/src/index.ts:2465
// Process complete lines in the buffer
const lines = buffer.split('\n')
buffer = lines.pop() || '' // Keep the last incomplete line in the buffer
for (const line of lines) {
const trimmedLine = line.trim()
if (!trimmedLine || trimmedLine === 'data: [DONE]') {
continue
}
if (trimmedLine.startsWith('data: ')) {
jsonStr = trimmedLine.slice(6)
} else if (trimmedLine.startsWith('error: ')) {
jsonStr = trimmedLine.slice(7)
const error = JSON.parse(jsonStr)
throw new Error(error.message)
} else {
// it should not normally reach here
throw new Error('Malformed chunk')
}
try {
const data = JSON.parse(jsonStr)
const chunk = data as chatCompletionChunk
yield chunk
} catch (e) {
logger.error('Error parsing JSON from stream or server error:', e)
// re‑throw so the async iterator terminates with an error
throw e
}
}
}
} finally {
reader.releaseLock()
}
}
View on GitHub (pinned to 7205d770c1)