santifer/career-ops · error · Error
Pinned model timed out after ${MODEL_TIMEOUT_MS / 1000}s
Error message
Pinned model timed out after ${MODEL_TIMEOUT_MS / 1000}s What it means
Thrown in the pinned-model branch of callOpenRouter() when the AbortController fires (signal: ctrl.signal) because the request exceeded MODEL_TIMEOUT_MS (15 seconds). The catch detects e.name === 'AbortError' and converts it into this explicit timeout message rather than a generic abort. The pinned-model path does not retry — it surfaces the timeout directly.
Source
Thrown at openrouter-runner.mjs:246
'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.'
);
}
// 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.');
View on GitHub (pinned to 9b17a8ac97)
Solutions
- Retry — timeouts are frequently transient.
- Pin a faster/smaller free model (CAREER_OPS_MODEL) or unset it so rotation can skip a slow model.
- Reduce the prompt size (trim cv.md context or JD) to lower time-to-first-token.
- If persistent, raise MODEL_TIMEOUT_MS in code only after confirming the model legitimately needs more time.
Defensive patterns
Strategy: retry
Try / catch
try {
return await callOpenRouter(systemPrompt, userMessage);
} catch (e) {
if (e.message.includes('Pinned model timed out')) {
// fall back to rotation which can skip the slow model
delete process.env.CAREER_OPS_MODEL;
return await callOpenRouter(systemPrompt, userMessage);
}
throw e;
} Prevention
- Pin smaller/faster free models when latency matters.
- Reduce prompt size to cut time-to-first-token.
- Prefer rotation so a single slow model is skipped.
- Only raise MODEL_TIMEOUT_MS after confirming a model legitimately needs more time.
When it happens
Trigger: CAREER_OPS_MODEL is set, the fetch to OpenRouter started but did not complete within 15000ms, so the abort signal fired. Happens with slow/large models, congested network, or an overloaded provider.
Common situations: Pinned a large/slow model; network latency or a flaky connection to openrouter.ai; provider under load; very large prompt causing slow time-to-first-token; CI runner with limited network throughput.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Timeout after ${MODEL_TIMEOUT_MS / 1000}s
- [models] Failed to fetch free model list: ${reason}. Check t
- HTTP ${resp.status}: ${t.slice(0, 120)}
- ${data.error.message}
- Empty response
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/85c596265565de25.
Report an issue: GitHub.