santifer/career-ops · error · Error
No free OpenRouter models are available. Model loading may h
Error message
No free OpenRouter models are available. Model loading may have failed, or your account currently has no free models.
What it means
Thrown by callOpenRouter() after loadFreeModels() returns an empty array — the model list loaded but contained zero free models. This is distinct from a fetch failure (error 67): the API call succeeded, it just yielded nothing usable. Without any models, the rotation loop has nothing to iterate, so it fails immediately.
Source
Thrown at openrouter-runner.mjs:257
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.'
);
}
// Build the active (non-blacklisted) model list in rotation order
const active = models.filter(m => !blacklistedModels.has(m));
if (active.length === 0) throw new Error('All loaded models have been blacklisted this session.');
for (let attempt = 0; attempt < active.length; attempt++) {
const model = active[(modelIndex % active.length + attempt) % active.length];
activeModel = model;
process.stdout.write(`[model] ${model} ... `);
try {
const body = JSON.stringify({
model,
messages: [
buildCachedSystemMessage(systemPrompt),
{ role: 'user', content: userMessage },View on GitHub (pinned to 9b17a8ac97)
Solutions
- Pin a specific known-good free model via CAREER_OPS_MODEL to bypass the list entirely (e.g. google/gemini-2.0-flash-exp:free).
- Re-run loadFreeModels (the empty list is often transient) or check https://openrouter.ai/models?max_price=0 for current free models.
- Check your OpenRouter account standing — some account states lose free-tier access.
- If the loader filter is the culprit, update the free-model detection in loadFreeModels.
Example fix
# before — empty free list node openrunner-runner.mjs ... # throws: No free OpenRouter models are available. # after — pin a known free model export CAREER_OPS_MODEL=google/gemini-2.0-flash-exp:free
Defensive patterns
Strategy: fallback
Validate before calling
const freeCount = await loadFreeModels().then(m => m.length).catch(() => -1);
if (freeCount === 0) {
process.env.CAREER_OPS_MODEL = 'google/gemini-2.0-flash-exp:free';
} Try / catch
try {
return await callOpenRouter(systemPrompt, userMessage);
} catch (e) {
if (e.message.startsWith('No free OpenRouter models are available')) {
process.env.CAREER_OPS_MODEL = 'google/gemini-2.0-flash-exp:free';
return await callOpenRouter(systemPrompt, userMessage);
}
throw e;
} Prevention
- Keep a known-good free model id handy to pin via CAREER_OPS_MODEL when the list is empty.
- Verify account standing and region support for free models on OpenRouter.
- Check https://openrouter.ai/models?max_price=0 periodically for current free offerings.
- Treat an empty list as transient first — retry before pinning.
When it happens
Trigger: loadFreeModels() returns []: OpenRouter's /models endpoint answered 2xx but no models matched the free-tier filter, or the account/region currently has no free models available. The `if (models.length === 0)` guard throws before rotation.
Common situations: OpenRouter changed its free-tier offering and the filter matches nothing; account in a region with no free models; transient empty response from /models; the free model ids changed naming and the loader's filter no longer matches.
Related errors
- [models] Failed to fetch free model list: ${reason}. Check t
- OPENROUTER_API_KEY not found. Copy .env.example to .env and
- All loaded models have been blacklisted this session.
- HTTP ${resp.status}: ${t.slice(0, 120)}
- Timeout after ${MODEL_TIMEOUT_MS / 1000}s
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/1a187bb0a9d29d91.
Report an issue: GitHub.