{"record":{"id":"d9340d76548e2aa7","repo":"odysseus-dev/odysseus","slug":"http-response-status","errorCode":null,"errorMessage":"HTTP ${response.status}","messagePattern":"HTTP \\$\\{response\\.status\\}","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"static/js/compare/stream.js","lineNumber":261,"sourceCode":"    }\n    const incognitoChk = document.getElementById('incognito-toggle');\n    if (incognitoChk && incognitoChk.checked) {\n      fd.append('incognito', 'true');\n    }\n    // Disable document tool and memory injection in compare mode\n    fd.append('no_documents', 'true');\n    fd.append('no_memory', 'true');\n    // Tell backend this is compare mode — strip all non-toggled tools\n    fd.append('compare_mode', 'true');\n    // Forward preset if selected\n    if (presetsModule && presetsModule.getSelectedPreset()) {\n      fd.append('preset_id', presetsModule.getSelectedPreset());\n    }\n\n    const response = await fetch(`${state.API_BASE}/api/chat_stream`, {\n      method: 'POST', body: fd, signal: ac.signal\n    });\n    if (!response.ok) throw new Error(`HTTP ${response.status}`);\n\n    const reader = response.body.getReader();\n    const decoder = new TextDecoder();\n    let buffer = '';\n\n    while (true) {\n      const { done, value } = await reader.read();\n      if (done) break;\n      _resetIdleTimeout();  // any chunk = stream is alive\n\n      buffer += decoder.decode(value, { stream: true });\n      const lines = buffer.split('\\n');\n      buffer = lines.pop() || '';\n\n      for (const line of lines) {\n        if (!line.startsWith('data: ')) continue;\n        const data = line.slice(6);\n        if (data === '[DONE]') break;","sourceCodeStart":243,"sourceCodeEnd":279,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/compare/stream.js#L243-L279","documentation":"Error thrown when the compare-mode streaming request POST /api/chat_stream returns a non-OK status before any SSE/stream data is read. The message is only 'HTTP <status>' — no response body is inspected, so the cause is opaque from the UI.","triggerScenarios":"POST /api/chat_stream (multipart with no_documents, no_memory, compare_mode, optional preset_id) returns 404 (unknown session), 400 (invalid preset_id), 422 (missing fields), or 500 (backend exception before the stream starts).","commonSituations":"Session id from the create call is stale or invalid; preset selected in the UI was deleted server-side; backend restarted mid-compare; reverse proxy (nginx) returns 502/504 before the stream begins; compare_mode flag unsupported on older backend.","solutions":["Reproduce with curl -i -X POST the same multipart body to read the status and error body the code discards","Verify the session id sent in the form actually exists (it should be the one returned by POST /api/session)","If a preset_id is appended, confirm that preset still exists server-side or drop it","Improve the error to include the body: read response.text() before throwing"],"exampleFix":"// before\nif (!response.ok) throw new Error(`HTTP ${response.status}`);\n\n// after\nif (!response.ok) {\n  const body = await response.text().catch(() => '');\n  let detail = body;\n  try { detail = JSON.parse(body).detail || body; } catch {}\n  throw new Error(`HTTP ${response.status}: ${String(detail).slice(0, 200)}`);\n}","handlingStrategy":"try-catch","validationCode":"if (!fd.get('session')) { throw new Error('No session id to stream against'); }\nif (presetsModule?.getSelectedPreset && presetsModule.getSelectedPreset() == null) fd.delete('preset_id');","typeGuard":null,"tryCatchPattern":"try {\n  const response = await fetch(`${state.API_BASE}/api/chat_stream`, { method: 'POST', body: fd, signal: ac.signal });\n  if (!response.ok) {\n    const body = await response.text().catch(() => '');\n    let detail = body;\n    try { detail = JSON.parse(body).detail || body; } catch {}\n    throw new Error(`HTTP ${response.status}: ${String(detail).slice(0, 200)}`);\n  }\n  // ... read stream\n} catch (e) {\n  if (e.name === 'AbortError') return; // user cancelled — not an error\n  showCompareError(e.message);\n}","preventionTips":["Always read the error body on non-OK stream responses before throwing","Distinguish AbortError from real failures in the catch","Verify the session id and preset id exist before starting the stream"],"tags":["fetch","http-error","streaming","chat-stream","compare-mode"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}