santifer/career-ops · warning · Error
Empty response
Error message
Empty response
What it means
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).
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.
Example fix
// before
const content = data.choices?.[0]?.message?.content ?? '';
if (!content) throw new Error('Empty response');
// after
const choice = data.choices?.[0];
const content = choice?.message?.content ?? '';
if (!content) {
throw new Error(`Empty response (finish_reason=${choice?.finish_reason ?? 'n/a'}, model=${data.model ?? 'unknown'})`);
} Defensive patterns
Strategy: retry
Type guard
function hasContent(data) {
const c = data?.choices?.[0]?.message?.content;
return typeof c === 'string' && c.length > 0;
} Try / catch
try {
result = await callOpenRouter(systemPrompt, userMessage);
} catch (err) {
if (err.message === 'Empty response' && attempt < MAX_ATTEMPTS) {
attempt++; // rotation will try the next free model
return callWithRotation(systemPrompt, userMessage, attempt);
}
throw err;
} Prevention
- 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.
When it happens
Trigger: data.choices is an empty array, the choice has no message, or message.content === '' — after a successful 200 and no data.error.
Common situations: 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.
Related errors
- ${data.error.message}
- HTTP
- All active models failed. Last error
- [models] Failed to fetch free model list
- No free models found in API response
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/b341c7b1d3731657.
Report an issue: GitHub.
Appendix: source
Thrown at openrouter-runner.mjs:264
const resp = await fetch(OPENROUTER_API_URL, {
method: 'POST',
headers: {
'Authorization': `Bearer ${key}`,
'Content-Type': 'application/json',
'HTTP-Referer': 'https://github.com/career-ops-hq/career-ops',
'X-Title': 'career-ops',
},
body,
signal: ctrl.signal,
});
if (!resp.ok) {
const t = await resp.text();
throw new Error(`HTTP ${resp.status}: ${t.slice(0, 120)}`);
}
const data = await resp.json();
if (data.error) throw new Error(data.error.message);
const content = data.choices?.[0]?.message?.content ?? '';
if (!content) throw new Error('Empty response');
console.log('OK');
const usage = normalizeOpenAIUsage(data.usage);
return { content, usage };
} catch (e) {
if (e.name === 'AbortError') throw new Error(`Pinned model timed out after ${MODEL_TIMEOUT_MS / 1000}s`);
throw e;
} finally {
clearTimeout(timerId);
}
}
const models = await loadFreeModels();
let lastError;
if (models.length === 0) {
throw new Error(
'No free OpenRouter models are available. Model loading may have failed, or your account currently has no free models.'
);View on GitHub (pinned to aac998c7ed)