{"record":{"id":"88d829cdf9e72fb0","repo":"santifer/career-ops","slug":"http-resp-status-t-slice-0-120","errorCode":null,"errorMessage":"HTTP ${resp.status}: ${t.slice(0, 120)}","messagePattern":"HTTP (.+?): (.+?)","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"openrouter-runner.mjs","lineNumber":259,"sourceCode":"      max_tokens: MAX_TOKENS,\n    });\n    const ctrl = new AbortController();\n    const timerId = setTimeout(() => ctrl.abort(), MODEL_TIMEOUT_MS);\n    try {\n      const resp = await fetch(OPENROUTER_API_URL, {\n        method: 'POST',\n        headers: {\n          'Authorization': `Bearer ${key}`,\n          'Content-Type':  'application/json',\n          'HTTP-Referer':  'https://github.com/career-ops-hq/career-ops',\n          'X-Title':       'career-ops',\n        },\n        body,\n        signal: ctrl.signal,\n      });\n      if (!resp.ok) {\n        const t = await resp.text();\n        throw new Error(`HTTP ${resp.status}: ${t.slice(0, 120)}`);\n      }\n      const data = await resp.json();\n      if (data.error) throw new Error(data.error.message);\n      const content = data.choices?.[0]?.message?.content ?? '';\n      if (!content) throw new Error('Empty response');\n      console.log('OK');\n      const usage = normalizeOpenAIUsage(data.usage);\n      return { content, usage };\n    } catch (e) {\n      if (e.name === 'AbortError') throw new Error(`Pinned model timed out after ${MODEL_TIMEOUT_MS / 1000}s`);\n      throw e;\n    } finally {\n      clearTimeout(timerId);\n    }\n  }\n\n  const models = await loadFreeModels();\n  let lastError;","sourceCodeStart":241,"sourceCodeEnd":277,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/openrouter-runner.mjs#L241-L277","documentation":"Thrown inside callOpenRouter()'s request loop when the chat-completions endpoint returns a non-ok HTTP status. Unlike the models-list check, this includes the first 120 characters of the response body, which usually contains OpenRouter's JSON error explaining the model/status. Each failed model in the rotation is typically blacklisted and the next candidate tried.","triggerScenarios":"POST to the completions endpoint returns 400 (malformed request/unsupported param for that model), 401/402 (key or credits), 404 (model id no longer exists), 429 (rate limited), or 5xx — with body text captured via resp.text().","commonSituations":"A free model was deprecated mid-session (404); per-model rate limits on free tier (429); provider upstream error surfaced as 502; moderation flagging the prompt (400).","solutions":["Read the 120-char body snippet in the message — it names the actual problem (auth, credits, model id, moderation).","401/402: fix OPENROUTER_API_KEY or add credits; 429: the rotation should try the next model — if all fail, slow down.","404: the model was removed from OpenRouter; let loadFreeModels refresh the list on a fresh run.","400: check the request body — some free models reject params like temperature or system role.","Check OpenRouter status page on persistent 5xx."],"exampleFix":"// before\nif (!resp.ok) {\n  const t = await resp.text();\n  throw new Error(`HTTP ${resp.status}: ${t.slice(0, 120)}`);\n}\n// after\nif (!resp.ok) {\n  const t = await resp.text();\n  let detail = t.slice(0, 120);\n  try { detail = JSON.parse(t)?.error?.message ?? detail; } catch {}\n  const err = new Error(`HTTP ${resp.status}: ${detail}`);\n  err.status = resp.status;\n  throw err; // caller can branch on 429 vs 401 vs 404\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  result = await callOpenRouter(systemPrompt, userMessage);\n} catch (err) {\n  const m = /HTTP (\\d{3})/.exec(err.message);\n  const status = m ? Number(m[1]) : null;\n  if (status === 429 || (status && status >= 500)) {\n    await sleep(backoff(attempt++)); // transient — retry; rotation blacklists the model\n  } else {\n    throw err; // 401/402/400 need human action\n  }\n}","preventionTips":["Always read the 120-char body snippet — OpenRouter names the failing model and cause there.","Retry 429/5xx automatically with backoff; never retry 401/402 blindly.","Keep the model rotation healthy so a dead model is skipped automatically.","Log status codes separately to spot per-model patterns."],"tags":["openrouter","http-error","api","model-rotation"],"backgroundTag":"api-error-response","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}