{"record":{"id":"fdf0f0964e31b008","repo":"santifer/career-ops","slug":"timeout-after-model-timeout-ms-1000-s","errorCode":null,"errorMessage":"Timeout after ${MODEL_TIMEOUT_MS / 1000}s","messagePattern":"Timeout after (.+?)s","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"openrouter-runner.mjs","lineNumber":301,"sourceCode":"      try {\n        const resp = await fetch(OPENROUTER_API_URL, {\n          method: 'POST',\n          headers: {\n            'Authorization': `Bearer ${key}`,\n            'Content-Type':  'application/json',\n            'HTTP-Referer':  'https://github.com/santifer/career-ops',\n            'X-Title':       'career-ops',\n          },\n          body,\n          signal: controller.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        data = await resp.json();\n      } catch (e) {\n        if (e.name === 'AbortError') throw new Error(`Timeout after ${MODEL_TIMEOUT_MS / 1000}s`);\n        throw e;\n      } finally {\n        clearTimeout(timerId);\n      }\n      if (data.error) throw new Error(data.error.message);\n\n      const content = data.choices?.[0]?.message?.content ?? '';\n      if (!content) throw new Error('Empty response');\n\n      const usage = normalizeOpenAIUsage(data.usage);\n\n      modelIndex = (modelIndex + attempt + 1) % active.length;\n      console.log('OK');\n      return { content, usage };\n\n    } catch (e) {\n      lastError = e;\n      const msg = e.message.split('\\n')[0];","sourceCodeStart":283,"sourceCodeEnd":319,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/openrouter-runner.mjs#L283-L319","documentation":"Thrown inside the rotation loop of callOpenRouter() when the AbortController fires because a rotated model's request exceeded MODEL_TIMEOUT_MS (15 seconds). The catch converts AbortError into this timeout message. Like error 75, this is caught by the loop and feeds retry logic — it propagates only if every model ultimately fails.","triggerScenarios":"During rotation, a model's fetch did not complete within 15000ms and the abort signal fired. Happens with slow large models, network congestion, or an overloaded provider for that specific model.","commonSituations":"Rotating onto a slow/large free model; network latency spike; a particular provider is slow under load; large prompt inflating time-to-first-token.","solutions":["Let rotation continue — the loop will try the next, faster model.","Reduce prompt size to lower per-model latency.","Pin a known-fast free model via CAREER_OPS_MODEL.","If timeouts hit every model, suspect the network path to openrouter.ai."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Rotation handles timeouts internally. Only act if all models time out (error 78).\n// For a single-model timeout, reduce prompt size or pin a faster model.","preventionTips":["Prefer rotation so a single slow model is skipped automatically.","Trim prompt size to reduce per-model latency.","Pin a known-fast free model for latency-sensitive runs.","Investigate the network path if every model times out."],"tags":["api","openrouter","timeout","network","model-rotation"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}