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

  1. Raise MODEL_TIMEOUT_MS to a value that accommodates slow free models (e.g. 60–120s)
  2. Rely on rotation: the runner tries the next model; verify timeout errors actually lead to blacklisting/rotation
  3. Check network connectivity and any proxy/firewall stalling outbound HTTPS to openrouter.ai
  4. Reduce max_tokens/prompt size so generation finishes within the timeout
  5. 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

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.

Related errors


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)