{"record":{"id":"cef46fb06f8998be","repo":"musistudio/claude-code-router","slug":"openrouter-endpoints-request-is-cooling-down-after","errorCode":null,"errorMessage":"OpenRouter endpoints request is cooling down after failure (${Math.ceil(remainingMs / 1000)}s left): ${failure.message}","messagePattern":"OpenRouter endpoints request is cooling down after failure \\((.+?)s left\\): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/core/src/plugins/built-ins/openrouter-discount-provider-router.ts","lineNumber":311,"sourceCode":"\nasync function loadModelEndpoints(\n  provider: GatewayProviderConfig,\n  model: string,\n  settings: OpenRouterDiscountRoutingSettings\n): Promise<unknown[]> {\n  const apiRoot = openRouterApiRoot(provider);\n  const cacheKey = `${apiRoot}:${model}`;\n  const cached = endpointCache.get(cacheKey);\n  const now = Date.now();\n  if (cached && now - cached.fetchedAt < settings.endpointTtlMs) {\n    return cached.endpoints;\n  }\n  const failure = endpointFailureCache.get(cacheKey);\n  if (failure) {\n    const cooldownAge = now - failure.failedAt;\n    if (cooldownAge < endpointFailureCooldownMs) {\n      const remainingMs = endpointFailureCooldownMs - cooldownAge;\n      throw new Error(`OpenRouter endpoints request is cooling down after failure (${Math.ceil(remainingMs / 1000)}s left): ${failure.message}`);\n    }\n    endpointFailureCache.delete(cacheKey);\n  }\n  const inflight = endpointInflight.get(cacheKey);\n  if (inflight) {\n    return inflight;\n  }\n\n  const [author, ...slugParts] = model.split(\"/\");\n  const slug = slugParts.join(\"/\");\n  if (!author || !slug) {\n    throw new Error(`OpenRouter model id must be author/slug: ${model}`);\n  }\n\n  const url = `${trimRight(apiRoot, \"/\")}/api/v1/models/${encodeURIComponent(author)}/${encodeURIComponent(slug)}/endpoints`;\n  const promise = fetchModelEndpoints(url).then((endpoints) => {\n    endpointFailureCache.delete(cacheKey);\n    if (endpoints.length > 0) {","sourceCodeStart":293,"sourceCodeEnd":329,"githubUrl":"https://github.com/musistudio/claude-code-router/blob/99f24806c6a2c660b16e53e95211c517448a6c90/packages/core/src/plugins/built-ins/openrouter-discount-provider-router.ts#L293-L329","documentation":"The OpenRouter endpoints fetch implements a failure cooldown: after a failed request, subsequent calls for the same cache key fail fast with the remaining cooldown time instead of hammering the API. The original failure message is embedded.","triggerScenarios":"Calling loadModelEndpoints again within endpointFailureCooldownMs after a previous failure for the same model+apiRoot key.","commonSituations":"Retry loops or multiple concurrent completions calling endpoints for the same model right after a network/HTTP failure.","solutions":["Wait for the stated cooldown to elapse before retrying","Fix the underlying failure (network, API key, rate limit) shown in the embedded message","Cache endpoint results upstream so the call is not repeated per request"],"exampleFix":"// before\nretry(() => loadModelEndpoints(model), 5); // hammers cooldown\n// after\nconst endpoints = await waitForCooldownThenLoad(model); // respects remainingMs","handlingStrategy":"retry","validationCode":"null","typeGuard":"const isCooldownError = (e: unknown): boolean => e instanceof Error && e.message.includes(\"cooling down after failure\");","tryCatchPattern":"try { return await loadModelEndpoints(model); } catch (e) { if (isCooldownError(e)) { const secs = Number(/\\((\\d+)s left\\)/.exec(e.message)?.[1] ?? 0); await sleep((secs + 1) * 1000); return await loadModelEndpoints(model); } throw e; }","preventionTips":["Cache endpoint lookups per model","Honor cooldown instead of tight retry loops"],"tags":["openrouter","rate-limit","cooldown","retry"],"backgroundTag":"api-cooldown-backoff","analyzedSha":"99f24806c6a2c660b16e53e95211c517448a6c90","analyzedAt":"2026-08-27T04:11:01.184Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}