{"record":{"id":"8b5edfe9f25bde4e","repo":"janhq/jan","slug":"malformed-chunk","errorCode":null,"errorMessage":"Malformed chunk","messagePattern":"Malformed chunk","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"extensions/llamacpp-extension/src/index.ts","lineNumber":3700,"sourceCode":"        // Process complete lines in the buffer\n        const lines = buffer.split('\\n')\n        buffer = lines.pop() || '' // Keep the last incomplete line in the buffer\n\n        for (const line of lines) {\n          const trimmedLine = line.trim()\n          if (!trimmedLine || trimmedLine === 'data: [DONE]') {\n            continue\n          }\n\n          if (trimmedLine.startsWith('data: ')) {\n            jsonStr = trimmedLine.slice(6)\n          } else if (trimmedLine.startsWith('error: ')) {\n            jsonStr = trimmedLine.slice(7)\n            const error = JSON.parse(jsonStr)\n            throw new Error(error.message)\n          } else {\n            // it should not normally reach here\n            throw new Error('Malformed chunk')\n          }\n          try {\n            const data = JSON.parse(jsonStr)\n            const chunk = data as chatCompletionChunk\n\n            yield chunk\n          } catch (e) {\n            logger.error('Error parsing JSON from stream or server error:', e)\n            // re‑throw so the async iterator terminates with an error\n            throw e\n          }\n        }\n      }\n    } finally {\n      reader.releaseLock()\n    }\n  }\n","sourceCodeStart":3682,"sourceCodeEnd":3718,"githubUrl":"https://github.com/janhq/jan/blob/fad3f12a147d138388a66f0d92a02b2675f65294/extensions/llamacpp-extension/src/index.ts#L3682-L3718","documentation":"Thrown by the SSE line parser when a non-empty trimmed line is neither 'data: [DONE]', does not start with 'data: ', nor with 'error: '. The OpenAI-compatible SSE protocol the router speaks only emits those line shapes; anything else means the upstream is emitting a non-conformant event (or the line splitter got out of sync). The parser deliberately fails fast rather than silently dropping chunks.","triggerScenarios":"Router/upstream emits a comment line ': ping' (SSE comment) or an event field without a prefix. A keep-alive newline/byte corrupted the framing. A proxy rewrote the stream (added HTML, changed prefixes). Wrong endpoint hit (non-SSE JSON returned line-by-line). Buffer split left a stray token after a partial line. A 'data:' (no space) or 'data:  ' variant that does not match the exact prefix.","commonSituations":"CDN/proxy injecting heartbeat comments. Misconfigured reverse proxy not passing SSE through untouched. Talking to an OpenAI-compatible server with a slightly different SSE dialect (e.g. 'data:' with no trailing space, or 'event: ...'). A debug log line leaked into the response stream.","solutions":["Confirm the endpoint is a real OpenAI/SSE streaming endpoint and that nothing in between mutates the stream.","If the upstream sends legitimate SSE comments or alternate prefixes, pre-filter lines in your own SSE reader before passing to this parser.","Check for 'data:' (no space) vs 'data: ' mismatch - normalize upstream to the standard prefix.","Disable proxy buffering/rewriting and any injected heartbeat on the streaming route."],"exampleFix":"// before - upstream sends ': keepalive' comments -> Malformed chunk\nfor await (const c of await provider.chat(opts, ac)) { /* ... */ }\n// after - the parser is strict; ensure the route delivers only data:/error:/[DONE]\n// In nginx: proxy_buffering off; proxy_cache off; add_header X-Accel-Buffering no;\n// If you cannot change upstream, wrap with a sanitizing transform:\nasync function* sanitize(iter) {\n  for await (const c of iter) { /* re-emit only well-formed chunks */ yield c }\n}","handlingStrategy":"validation","validationCode":"// If you control the upstream, sanitize emitted lines to data:/error:/[DONE] only.\n// As a caller you cannot pre-validate; the guard is at the transport layer (proxy config).\n// Document the SSE contract for your upstream:\n//   allowed line prefixes: 'data: ', 'error: ', and the literal 'data: [DONE]'.","typeGuard":"function isAllowedSseLine(line: string): boolean {\n  const t = line.trim()\n  return !t || t === 'data: [DONE]' || t.startsWith('data: ') || t.startsWith('error: ')\n}","tryCatchPattern":"try { for await (const c of await provider.chat(opts, ac)) emit(c) }\ncatch (e) {\n  if (/Malformed chunk/.test(String(e))) { /* disable streaming, retry non-streaming */ const r = await provider.chat({ ...opts, stream: false }, ac); emit(r) }\n  else throw e\n}","preventionTips":["Ensure the SSE route passes through untouched: no buffering, no rewriting, no injected heartbeats.","If your upstream emits comments or alternate prefixes, normalize them upstream of this parser.","Talk only to OpenAI-compatible endpoints whose SSE dialect matches data:/error:/[DONE]."],"tags":["chat","streaming","sse","protocol","parsing","validation"],"backgroundTag":null,"analyzedSha":"fad3f12a147d138388a66f0d92a02b2675f65294","analyzedAt":"2026-08-12T20:33:47.516Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}