santifer/career-ops · error · Error
Timeout after s
Error message
Timeout after ${MODEL_TIMEOUT_MS / 1000}s What it means
Each model attempt is wrapped in an AbortController with a timer of MODEL_TIMEOUT_MS; if the fetch is aborted because the timer fired, the AbortError is converted into this clearer error. It means the OpenRouter model did not return a response within the configured per-model timeout.
Solutions
- Raise MODEL_TIMEOUT_MS to a value that accommodates slow free models (e.g. 60–120s)
- Rely on rotation: the runner tries the next model; verify timeout errors actually lead to blacklisting/rotation
- Check network connectivity and any proxy/firewall stalling outbound HTTPS to openrouter.ai
- Reduce max_tokens/prompt size so generation finishes within the timeout
- Implement an explicit retry with backoff for timeout failures, which are often transient
Example fix
// before
if (e.name === 'AbortError') throw new Error(`Timeout after ${MODEL_TIMEOUT_MS / 1000}s`);
// after
if (e.name === 'AbortError') {
if (attempt < active.length - 1) { console.log(`timeout after ${MODEL_TIMEOUT_MS / 1000}s, next model`); continue; }
throw new Error(`Timeout after ${MODEL_TIMEOUT_MS / 1000}s`);
} Defensive patterns
Strategy: retry
Try / catch
try {
return await callOpenRouter(prompt);
} catch (e) {
if (/^Timeout after/.test(e.message)) {
// transient: retry with next model / backoff
return withNextModelOrThrow();
}
throw e;
} Prevention
- Set MODEL_TIMEOUT_MS generously for slow free models (60s+)
- Treat timeouts as transient and rotate models rather than aborting the job
- Keep prompts/max_tokens modest so generations finish in time
- Monitor network path to openrouter.ai (DNS, proxy) in CI environments
When it happens
Trigger: A callOpenRouter request whose fetch neither resolves nor rejects before MODEL_TIMEOUT_MS elapses — slow/overloaded free model, network stall, or a hung streaming connection.
Common situations: Free models under heavy load queueing requests for minutes; no internet or DNS hang; MODEL_TIMEOUT_MS set too low for long generations (large max_tokens).
Understand the failure class
Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Pinned model timed out after
- All active models failed. Last error
- timed out after s. If your network is slow, retry or set to…
- HTTP
- [models] Failed to fetch free model list
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/fdf0f0964e31b008.
Report an issue: GitHub.
Appendix: source
Thrown at openrouter-runner.mjs:324
try {
const resp = await fetch(OPENROUTER_API_URL, {
method: 'POST',
headers: {
'Authorization': `Bearer ${key}`,
'Content-Type': 'application/json',
'HTTP-Referer': 'https://github.com/career-ops-hq/career-ops',
'X-Title': 'career-ops',
},
body,
signal: controller.signal,
});
if (!resp.ok) {
const t = await resp.text();
throw new Error(`HTTP ${resp.status}: ${t.slice(0, 120)}`);
}
data = await resp.json();
} catch (e) {
if (e.name === 'AbortError') throw new Error(`Timeout after ${MODEL_TIMEOUT_MS / 1000}s`);
throw e;
} finally {
clearTimeout(timerId);
}
if (data.error) throw new Error(data.error.message);
const content = data.choices?.[0]?.message?.content ?? '';
if (!content) throw new Error('Empty response');
const usage = normalizeOpenAIUsage(data.usage);
modelIndex = (modelIndex + attempt + 1) % active.length;
console.log('OK');
return { content, usage };
} catch (e) {
lastError = e;
const msg = e.message.split('\n')[0];View on GitHub (pinned to aac998c7ed)