santifer/career-ops · error · Error

Empty response

Error message

Empty response

What it means

Thrown in the pinned-model branch of callOpenRouter() when the HTTP call succeeded with no error object, but data.choices[0].message.content is empty (null, undefined, or empty string). This means the model returned a well-formed but contentless response — OpenRouter accepted the request and the model produced no output tokens.

Source

Thrown at openrouter-runner.mjs:241

      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(
      'No free OpenRouter models are available. Model loading may have failed, or your account currently has no free models.'
    );

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Retry once — empty responses are often transient single-call glitches.
  2. Switch pinned model (CAREER_OPS_MODEL) to a different provider/model, or unset to use rotation which skips the failing model.
  3. Inspect the full response (log data.choices[0]) to check finish_reason and any filter flags.
  4. Simplify/shorten the prompt to rule out an input-shape problem.
Defensive patterns

Strategy: retry

Try / catch

try {
  return await callOpenRouter(systemPrompt, userMessage);
} catch (e) {
  if (e.message === 'Empty response' && process.env.CAREER_OPS_MODEL) {
    // empty content from pinned model — retry on rotation
    delete process.env.CAREER_OPS_MODEL;
    return await callOpenRouter(systemPrompt, userMessage);
  }
  throw e;
}

Prevention

When it happens

Trigger: CAREER_OPS_MODEL is set, response is 2xx with no error, but the choices[0].message.content path resolves to '' or null. Common with content-filtered responses that return finish_reason without text, or a provider hiccup yielding zero output.

Common situations: Content filtered at the provider level (finish_reason: 'content_filter') returning empty text; model glitch; very rare provider routing issue; the prompt was malformed in a way that produced no generation.

Related errors


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