{"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":"http","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"openrouter-runner.mjs","lineNumber":269,"sourceCode":"          'HTTP-Referer':  'https://github.com/career-ops-hq/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":251,"sourceCodeEnd":287,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/openrouter-runner.mjs#L251-L287","documentation":"Rewritten inside callOpenRouter()'s catch: any AbortError from the fetch's AbortController (the MODEL_TIMEOUT_MS deadline firing) is converted into this human-readable timeout message naming the configured limit in seconds. It means the pinned/selected model did not return within the per-request timeout.","triggerScenarios":"A slow free model takes longer than MODEL_TIMEOUT_MS to stream/complete its response, or the connection hangs — the abort signal cancels the fetch and the AbortError is translated here.","commonSituations":"Large prompts against a congested free-tier model; provider slowness or partial outage; MODEL_TIMEOUT_MS set too low for the workload; network stall without RST keeping the request hanging.","solutions":["Retry — the rotation should blacklist the slow model and try the next one.","Increase MODEL_TIMEOUT_MS if your prompts legitimately need more time.","Prefer faster models in PROVIDER_PRIORITY or pin a model known to be responsive.","Check the provider/OpenRouter status on correlated timeouts.","Reduce prompt size to cut generation latency."],"exampleFix":"// before\nsignal: ctrl.signal,\n// after (per-attempt timeout scaled to prompt size)\nconst timeoutMs = Math.max(MODEL_TIMEOUT_MS, estimateTokens(body) * 50);\nsignal: AbortSignal.timeout(timeoutMs),","handlingStrategy":"retry","validationCode":"const estimatedMs = Math.ceil(promptChars / 3) * 60; // rough latency budget\nif (estimatedMs > MODEL_TIMEOUT_MS) console.warn(`Prompt may exceed the ${MODEL_TIMEOUT_MS}ms model timeout`);","typeGuard":null,"tryCatchPattern":"try {\n  result = await callOpenRouter(systemPrompt, userMessage);\n} catch (err) {\n  if (/timed out after/.test(err.message) && attempt < MAX_ATTEMPTS) {\n    attempt++; // rotation picks a faster model on the next try\n    return callWithRotation(systemPrompt, userMessage, attempt);\n  }\n  throw err;\n}","preventionTips":["Size MODEL_TIMEOUT_MS to your largest realistic prompt, not the average one.","Order PROVIDER_PRIORITY with historically fast models first.","Retry timeouts automatically — the rotation blacklists the slow model.","Shard very large prompts instead of sending one long request."],"tags":["openrouter","timeout","network","model-rotation"],"backgroundTag":"request-timeout","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}