{"record":{"id":"85c596265565de25","repo":"santifer/career-ops","slug":"pinned-model-timed-out-after-model-timeout-ms","errorCode":null,"errorMessage":"Pinned model timed out after ${MODEL_TIMEOUT_MS / 1000}s","messagePattern":"Pinned model timed out after (.+?)s","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"openrouter-runner.mjs","lineNumber":246,"sourceCode":"          'HTTP-Referer':  'https://github.com/santifer/career-ops',\n          'X-Title':       'career-ops',\n        },\n        body,\n        signal: ctrl.signal,\n      });\n      if (!resp.ok) {\n        const t = await resp.text();\n        throw new Error(`HTTP ${resp.status}: ${t.slice(0, 120)}`);\n      }\n      const data = await resp.json();\n      if (data.error) throw new Error(data.error.message);\n      const content = data.choices?.[0]?.message?.content ?? '';\n      if (!content) throw new Error('Empty response');\n      console.log('OK');\n      const usage = normalizeOpenAIUsage(data.usage);\n      return { content, usage };\n    } catch (e) {\n      if (e.name === 'AbortError') throw new Error(`Pinned model timed out after ${MODEL_TIMEOUT_MS / 1000}s`);\n      throw e;\n    } finally {\n      clearTimeout(timerId);\n    }\n  }\n\n  const models = await loadFreeModels();\n  let lastError;\n\n  if (models.length === 0) {\n    throw new Error(\n      'No free OpenRouter models are available. Model loading may have failed, or your account currently has no free models.'\n    );\n  }\n  // Build the active (non-blacklisted) model list in rotation order\n  const active = models.filter(m => !blacklistedModels.has(m));\n  if (active.length === 0) throw new Error('All loaded models have been blacklisted this session.');\n","sourceCodeStart":228,"sourceCodeEnd":264,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/openrouter-runner.mjs#L228-L264","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  return await callOpenRouter(systemPrompt, userMessage);\n} catch (e) {\n  if (e.message.includes('Pinned model timed out')) {\n    // fall back to rotation which can skip the slow model\n    delete process.env.CAREER_OPS_MODEL;\n    return await callOpenRouter(systemPrompt, userMessage);\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["api","openrouter","timeout","network","pinned-model"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}