santifer/career-ops · error · Error
${data.error.message}
Error message
${data.error.message} What it means
Thrown in the pinned-model branch of callOpenRouter() when the OpenRouter response JSON contains a top-level error object (data.error) despite a 2xx status, or an error surfaced in the body. The message is verbatim data.error.message from OpenRouter — typically model-side content/policy errors, context-length issues, or provider routing failures.
Source
Thrown at openrouter-runner.mjs:239
const timerId = setTimeout(() => ctrl.abort(), MODEL_TIMEOUT_MS);
try {
const resp = await fetch(OPENROUTER_API_URL, {
method: 'POST',
headers: {
'Authorization': `Bearer ${key}`,
'Content-Type': 'application/json',
'HTTP-Referer': 'https://github.com/santifer/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(View on GitHub (pinned to 9b17a8ac97)
Solutions
- Read data.error.message — it usually states the exact cause (context length, moderation, provider error).
- Shorten the prompt / JD input if it's a context-length error; trim cv.md context or the JD payload.
- Switch the pinned model (CAREER_OPS_MODEL) to one with a larger context window or different provider, or unset it to use rotation.
- Retry once for transient provider errors; if it persists, the model may be down on OpenRouter's side.
Defensive patterns
Strategy: retry
Try / catch
try {
return await callOpenRouter(systemPrompt, userMessage);
} catch (e) {
// provider-returned error body on the pinned path
if (process.env.CAREER_OPS_MODEL && e.message) {
// switch model or shorten prompt, then retry once
delete process.env.CAREER_OPS_MODEL;
return await callOpenRouter(shorten(systemPrompt), userMessage);
}
throw e;
} Prevention
- Keep prompts within the pinned model's context window; trim cv.md context for long JDs.
- Avoid content that trips provider safety filters.
- Prefer rotation so one provider's error auto-skips to another.
- Log data.choices[0].finish_reason to diagnose content-filter empty responses.
When it happens
Trigger: CAREER_OPS_MODEL is set, the HTTP call succeeded (resp.ok), resp.json() returned an object with a non-null `error` field. OpenRouter sometimes returns 200 with an error body when the upstream provider rejected the request (moderation, context overflow, provider outage).
Common situations: Input prompt exceeded the pinned model's context window; content tripped the provider's safety filter; upstream provider (Google/OpenAI/etc.) returned an error that OpenRouter forwarded; model temporarily unavailable on the provider side.
Related errors
- Empty response
- HTTP ${resp.status}: ${t.slice(0, 120)}
- Pinned model timed out after ${MODEL_TIMEOUT_MS / 1000}s
- [models] Failed to fetch free model list: ${reason}. Check t
- OPENROUTER_API_KEY not found. Copy .env.example to .env and
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/a72a9a280b591522.
Report an issue: GitHub.