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

  1. Read data.error.message — it usually states the exact cause (context length, moderation, provider error).
  2. Shorten the prompt / JD input if it's a context-length error; trim cv.md context or the JD payload.
  3. Switch the pinned model (CAREER_OPS_MODEL) to one with a larger context window or different provider, or unset it to use rotation.
  4. 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

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


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/a72a9a280b591522. Report an issue: GitHub.