{"record":{"id":"14bb57b8f779384b","repo":"abhigyanpatwari/GitNexus","slug":"server","errorCode":"server","errorMessage":"No response body","messagePattern":"No response body","errorType":"http","errorClass":"BackendError","httpStatus":null,"severity":"error","filePath":"gitnexus-web/src/services/backend-client.ts","lineNumber":757,"sourceCode":"  }\n\n  const combined = new Uint8Array(downloaded);\n  let offset = 0;\n  for (const chunk of chunks) {\n    combined.set(chunk, offset);\n    offset += chunk.length;\n  }\n  return JSON.parse(new TextDecoder().decode(combined));\n};\n\nconst parseNdjsonGraphResponse = async (\n  response: Response,\n  onProgress?: (downloaded: number, total: number | null) => void,\n  maxNodes?: number,\n  maxEdges?: number,\n): Promise<{ nodes: GraphNode[]; relationships: GraphRelationship[] }> => {\n  if (!response.body) {\n    throw new BackendError('No response body', response.status, 'server');\n  }\n\n  const contentLength = response.headers.get('Content-Length');\n  const total = contentLength ? parseInt(contentLength, 10) : null;\n  const reader = response.body.getReader();\n  const decoder = new TextDecoder();\n  const nodes: GraphNode[] = [];\n  const relationships: GraphRelationship[] = [];\n  let buffer = '';\n  let downloaded = 0;\n\n  // Streaming circuit breaker (#2178): enforce the size limits mid-download as a\n  // backstop when pre-fetch stats were missing. Same `> threshold` comparison as\n  // decideSkipGraph. Throwing immediately after the offending push means a later\n  // error record in the same chunk is never reached — the breaker wins.\n  const overLimit = (): boolean =>\n    (typeof maxNodes === 'number' && nodes.length > maxNodes) ||\n    (typeof maxEdges === 'number' && relationships.length > maxEdges);","sourceCodeStart":739,"sourceCodeEnd":775,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/gitnexus-web/src/services/backend-client.ts#L739-L775","documentation":"BackendError (code 'server') thrown by parseNdjsonGraphResponse() when response.body is null/undefined on a graph stream request. The NDJSON streaming parser needs a readable body to parse node/relationship records line-by-line; an absent body means the server sent a response with no stream (malformed server behavior or a proxy stripping the body). Status is taken from response.status.","triggerScenarios":"A graph fetch (e.g. fetchGraph) returns a Response whose .body is null. This can happen when the server returns a response with Content-Length but no body, when an intermediary proxy strips the streaming body, or when the response was already consumed/locked by another reader.","commonSituations":"A misconfigured reverse proxy (nginx/cloudflare) buffering or dropping the streaming response body; the backend sent a non-streaming response for a streaming endpoint; the response body was already read by error-handling code before reaching the parser; a server bug returning an empty body for a graph request.","solutions":["Check the backend version — the streaming graph endpoint must be implemented and enabled","Inspect the response headers (Content-Type should be application/x-ndjson) and status in DevTools","If behind a proxy, disable response buffering for the /api/graph streaming route (e.g. proxy_buffering off in nginx)","Ensure no other code path consumed response.body before parseNdjsonGraphResponse runs"],"exampleFix":"// before — caller assumes the stream is always present\nconst resp = await fetchGraph(repo);\nconst { nodes, relationships } = await parseNdjsonGraphResponse(resp); // throws\n\n// after — guard against an absent body\nif (!resp.body) throw new Error('Backend returned no graph stream — check backend version/proxy');\nconst { nodes, relationships } = await parseNdjsonGraphResponse(resp);","handlingStrategy":"validation","validationCode":"// Check response.body and Content-Type before handing to the NDJSON parser\nif (!response.body) {\n  throw new Error('Backend returned no graph stream — check backend version / proxy buffering');\n}\nconst ct = response.headers.get('Content-Type') ?? '';\nif (!ct.includes('ndjson') && !ct.includes('json')) {\n  throw new Error(`Unexpected graph Content-Type: ${ct}`);\n}","typeGuard":"import { BackendError } from './services/backend-client.js';\nfunction isNoBodyError(e: unknown): e is BackendError {\n  return e instanceof BackendError && e.code === 'server' && /No response body/.test(e.message);\n}","tryCatchPattern":"try {\n  return await parseNdjsonGraphResponse(response);\n} catch (e) {\n  if (e instanceof BackendError && e.code === 'server' && /No response body/.test(e.message)) {\n    // backend or proxy stripped the stream — fall back to a non-streaming endpoint or chat-only\n    enterChatOnlyMode();\n    return { nodes: [], relationships: [] };\n  }\n  throw e;\n}","preventionTips":["Ensure the backend version supports the streaming graph endpoint","Disable response buffering on reverse proxies for the /api/graph route (nginx: proxy_buffering off)","Never consume response.body with another reader before parseNdjsonGraphResponse runs","Check Content-Type is application/x-ndjson before parsing"],"tags":["graph","streaming","ndjson","backend","server"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}