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

  1. Retry — timeouts are frequently transient.
  2. Pin a faster/smaller free model (CAREER_OPS_MODEL) or unset it so rotation can skip a slow model.
  3. Reduce the prompt size (trim cv.md context or JD) to lower time-to-first-token.
  4. 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

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

Related errors


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/85c596265565de25. Report an issue: GitHub.