{"record":{"id":"b341c7b1d3731657","repo":"santifer/career-ops","slug":"empty-response","errorCode":null,"errorMessage":"Empty response","messagePattern":"Empty response","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"openrouter-runner.mjs","lineNumber":264,"sourceCode":"      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;\n\n  if (models.length === 0) {\n    throw new Error(\n      'No free OpenRouter models are available. Model loading may have failed, or your account currently has no free models.'\n    );","sourceCodeStart":246,"sourceCodeEnd":282,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/openrouter-runner.mjs#L246-L282","documentation":"Thrown when a completions response parses successfully but `data.choices[0].message.content` is undefined or an empty string. A 200 response with no generated content usually means the model produced no output (e.g. all tokens went to a reasoning/none finish, the provider stripped content, or output was filtered).","triggerScenarios":"data.choices is an empty array, the choice has no message, or message.content === '' — after a successful 200 and no data.error.","commonSituations":"Some free models return choices with content in nonstandard fields or empty content when the finish reason is 'length' with zero produced tokens; provider-side content filtering; model returning only a tool/reasoning block.","solutions":["Retry — the rotation should blacklist the empty-responder and try the next free model.","Log the full data.choices[0] object once to see whether content lives in a different field for that model.","Lower max_tokens constraints or check finish_reason; 'length' with no content means the prompt consumed the whole budget.","Exclude chronically empty models from PROVIDER_PRIORITY or blacklist them persistently.","If a specific model repeatedly returns empty, pin a different model instead of relying on rotation."],"exampleFix":"// before\nconst content = data.choices?.[0]?.message?.content ?? '';\nif (!content) throw new Error('Empty response');\n// after\nconst choice = data.choices?.[0];\nconst content = choice?.message?.content ?? '';\nif (!content) {\n  throw new Error(`Empty response (finish_reason=${choice?.finish_reason ?? 'n/a'}, model=${data.model ?? 'unknown'})`);\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":"function hasContent(data) {\n  const c = data?.choices?.[0]?.message?.content;\n  return typeof c === 'string' && c.length > 0;\n}","tryCatchPattern":"try {\n  result = await callOpenRouter(systemPrompt, userMessage);\n} catch (err) {\n  if (err.message === 'Empty response' && attempt < MAX_ATTEMPTS) {\n    attempt++; // rotation will try the next free model\n    return callWithRotation(systemPrompt, userMessage, attempt);\n  }\n  throw err;\n}","preventionTips":["Check finish_reason alongside content to distinguish 'length' cutoffs from true empties.","Persistently blacklist models that repeatedly return empty content.","Set max_tokens above the prompt's token count so the model has room to answer.","Log one full failing response body to catch nonstandard content fields."],"tags":["openrouter","api","empty-response","model-rotation"],"backgroundTag":"empty-response-body","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"}