{"record":{"id":"84f7fcb0016eccf2","repo":"Wei-Shaw/sub2api","slug":"http-error-status-response-status","errorCode":null,"errorMessage":"HTTP error! status: ${response.status}","messagePattern":"HTTP error! status: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/components/account/AccountTestModal.vue","lineNumber":440,"sourceCode":"    const url = buildApiUrl(`/admin/accounts/${props.account.id}/test`)\n\n    // Use fetch with streaming for SSE since EventSource doesn't support POST\n    const response = await fetch(url, {\n      method: 'POST',\n      headers: {\n        Authorization: `Bearer ${localStorage.getItem('auth_token')}`,\n        'Content-Type': 'application/json'\n      },\n      body: JSON.stringify({\n        model_id: selectedModelId.value,\n        prompt: supportsImageTest.value ? testPrompt.value.trim() : '',\n        mode: isOpenAIAccount.value ? testMode.value : 'default'\n      }),\n      signal: abortController.signal\n    })\n\n    if (!response.ok) {\n      throw new Error(`HTTP error! status: ${response.status}`)\n    }\n\n    const reader = response.body?.getReader()\n    if (!reader) {\n      throw new Error('No response body')\n    }\n\n    const decoder = new TextDecoder()\n    let buffer = ''\n\n    while (true) {\n      const { done, value } = await reader.read()\n      if (done) break\n\n      buffer += decoder.decode(value, { stream: true })\n      const lines = buffer.split('\\n')\n      buffer = lines.pop() || ''\n","sourceCodeStart":422,"sourceCodeEnd":458,"githubUrl":"https://github.com/Wei-Shaw/sub2api/blob/073e92d17178a1ccdb0a27017f572f10c9c7ab62/frontend/src/components/account/AccountTestModal.vue#L422-L458","documentation":"In frontend/src/components/account/AccountTestModal.vue:440, a fetch() to the account-test streaming endpoint checks response.ok; any non-2xx status throws a generic `HTTP error! status: ${response.status}`. Because the response body (which the API uses for error detail) is discarded, the operator only learns the status code. This is the account-owner (non-admin) test modal.","triggerScenarios":"POST to the model test endpoint with a Bearer token from localStorage returns 401 (expired token), 403 (no permission for that model_id), 400 (empty/invalid model_id or prompt), 429 (rate limit), or 502/504 (upstream provider failing). The thrown error surfaces the numeric status only.","commonSituations":"Testing an account whose API key was revoked; selecting a model the account tier cannot access; expired localStorage auth_token after long idle; upstream OpenAI-compatible endpoint down; abort signal racing a 499-style disconnect.","solutions":["Read the error body before throwing: `const detail = await response.text()` and include it in the thrown Error so the real API message is shown.","For 401, route the user to re-login instead of showing a raw error.","For 429/5xx, add retry-with-backoff or a clear 'provider temporarily unavailable' message.","Verify auth_token exists in localStorage and is still valid before opening the modal."],"exampleFix":"// before\nif (!response.ok) {\n  throw new Error(`HTTP error! status: ${response.status}`)\n}\n\n// after\nif (!response.ok) {\n  const text = await response.text().catch(() => '')\n  let detail = text\n  try { detail = JSON.parse(text)?.error?.message || text } catch {}\n  throw new Error(detail ? `${detail} (HTTP ${response.status})` : `HTTP error! status: ${response.status}`)\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { await streamTest(); }\ncatch (e) {\n  const m = String(e?.message);\n  const status = m.match(/status: (\\d+)$/)?.[1];\n  if (status === '401') { await relogin(); return; }\n  showError(m);\n}","preventionTips":["Always read response body text before throwing so server error detail is not discarded","Check localStorage auth_token presence/freshness before starting the test","Map 401/403/429/5xx to distinct user-facing messages"],"tags":["http","fetch","streaming","error-handling","frontend"],"backgroundTag":null,"analyzedSha":"073e92d17178a1ccdb0a27017f572f10c9c7ab62","analyzedAt":"2026-08-15T14:33:00.750Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}