santifer/career-ops · critical · Error
All active models failed. Last error
Error message
All ${active.length} active models failed. Last error: ${lastError?.message} What it means
callOpenRouter iterates over the pool of enabled OpenRouter models (after blacklisting), retrying each with rotation on failure. If every active model attempt fails — due to HTTP errors, rate limits, timeouts, or empty responses — it abandons retrying and throws this aggregate error carrying the last underlying error message. It means the LLM call could not be completed by any model in the configured pool, so the caller has no usable completion.
Solutions
- Read the 'Last error:' suffix in the message — it names the real cause (e.g. HTTP 401 vs 402 vs 429) and fix that first
- Verify OPENROUTER_API_KEY is set and valid, and that the account has credits (OpenRouter dashboard)
- Check the configured model names against https://openrouter.ai/models — replace any deprecated slugs
- Inspect and reset the persisted model blacklist (saveBlacklist target file); blacklisted models are excluded from 'active' and may leave the pool too small
- Confirm outbound network access to openrouter.ai (proxy/firewall/DNS) and retry later if the cause was a transient rate limit
Example fix
// before: single model pool, all fail MODELS = ['openai/gpt-4o']; // after: diversify the pool and keep it valid MODELS = [ 'openai/gpt-4o', 'anthropic/claude-3.5-sonnet', 'google/gemini-2.0-flash-001', ]; // plus: export OPENROUTER_API_KEY=sk-or-... (fresh key, funded account)
Defensive patterns
Strategy: retry
Validate before calling
function canAttemptCall() {
if (!process.env.OPENROUTER_API_KEY) throw new Error('OPENROUTER_API_KEY not set');
if (activeModels.length === 0) throw new Error('No active models left in pool (all blacklisted?)');
return true;
} Try / catch
try {
const { content } = await callOpenRouter(prompt);
} catch (e) {
if (e.message.startsWith('All ') && e.message.includes('active models failed')) {
// inspect lastError suffix: 401/402 → fix key/credits; 429 → back off longer;
// timeout → shrink prompt. Then retry with fresh models or abort the run.
console.error('LLM pool exhausted:', e.message.match(/Last error: (.*)/)?.[1]);
} else throw e;
} Prevention
- Check the OpenRouter key and credit balance at startup before batch runs
- Keep a diverse model pool (different providers) so a single provider outage doesn't empty it
- Periodically clear stale entries from the persisted blacklist file
- Watch for repeated 'FAILED (HTTP 429)' logs and add long backoff before the pool drains
- Size prompts below the context limit of the smallest model in the pool
When it happens
Trigger: Any callOpenRouter invocation where all models in the active pool fail: OpenRouter API returns non-2xx for every model (401 bad OPENROUTER_API_KEY, 402 insufficient credits, 403 model moderation/availability, 429 persistent rate limit), every request exceeds MODEL_TIMEOUT_MS, every response has data.error set, or every response has an empty choices[0].message.content.
Common situations: Expired or missing OpenRouter API key, exhausted credits, all configured models deprecated/removed from OpenRouter, no network access or corporate proxy blocking openrouter.ai, prompts too large for every model in the pool, or a persistent 429 that blacklisted all models one by one until the pool was empty.
Understand the failure class
Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.
Related errors
- [models] Failed to fetch free model list
- arbeitsagentur: all keyword request(s) failed
- archive render skipped — Greenhouse API unreachable
- ${data.error.message}
- Empty response
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/8721ca922d62df12.
Report an issue: GitHub.
Appendix: source
Thrown at openrouter-runner.mjs:368
console.log(`SKIP (blacklisted: ${msg})`);
} else if (is429) {
rateLimitCounts[model] = (rateLimitCounts[model] ?? 0) + 1;
if (rateLimitCounts[model] >= 3) {
blacklistedModels.add(model);
saveBlacklist(blacklistedModels);
console.log(`SKIP (auto-blacklisted: persistent 429)`);
} else {
console.log(`FAILED (HTTP 429 [${rateLimitCounts[model]}/3])`);
await new Promise(r => setTimeout(r, 800));
}
} else {
console.log(`FAILED (${msg})`);
await new Promise(r => setTimeout(r, 800));
}
}
}
throw new Error(`All ${active.length} active models failed. Last error: ${lastError?.message}`);
}
// ---------------------------------------------------------------------------
// Context loading
// ---------------------------------------------------------------------------
function loadContext() {
return {
cv: readFile('cv.md') ?? 'CV not found.',
profile: readFile('config/profile.yml') ?? '',
shared: readFile('modes/_shared.md') ?? '',
profileMode: readFile('modes/_profile.md') ?? '',
};
}
export function buildSystemPrompt(modeContent, ctx) {
const languageInstruction = outputLanguageInstruction(parseOutputLanguage(ctx.profile));
return [
ctx.shared,View on GitHub (pinned to aac998c7ed)