{"record":{"id":"d786b4cc25b0e48f","repo":"thedotmack/claude-mem","slug":"classifyopenroutererror-cause-err","errorCode":null,"errorMessage":"classifyOpenRouterError({ cause: err })","messagePattern":"classifyOpenRouterError\\((.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/services/worker/OpenRouterProvider.ts","lineNumber":542,"sourceCode":"    const messages = this.conversationToOpenAIMessages(history);\n    const totalChars = history.reduce((sum, m) => sum + m.content.length, 0);\n    const estimatedTokens = this.estimateTokens(messages.map(m => m.content).join(''));\n\n    logger.debug('SDK', `Querying OpenRouter multi-turn (${model})`, {\n      turns: history.length,\n      totalChars,\n      estimatedTokens\n    });\n\n    let priorRequestId: string | null = null;\n\n    const data = await withRetry<OpenRouterResponse>(async (attemptSignal) => {\n      let response: Response;\n      try {\n        response = await this.fetchChatCompletion(apiUrl, apiKey, model, fallbackModels, messages, siteUrl, appName, priorRequestId, attemptSignal, plainText);\n      } catch (networkError: unknown) {\n        const err = networkError instanceof Error ? networkError : new Error(String(networkError));\n        throw classifyOpenRouterError({ cause: err });\n      }\n\n      const requestId = response.headers.get('x-request-id') ?? response.headers.get('x-openrouter-request-id');\n      if (requestId) {\n        priorRequestId = requestId;\n      } else {\n        logger.debug('SDK', 'OpenRouter response missing request-id header; retry dedup is best-effort');\n      }\n\n      if (!response.ok) {\n        const errorText = await response.text();\n        throw classifyOpenRouterError({\n          status: response.status,\n          bodyText: errorText,\n          headers: response.headers,\n          cause: new Error(`OpenRouter API error: ${response.status} - ${errorText}`),\n          ...(requestId ? { requestId } : {}),\n        });","sourceCodeStart":524,"sourceCodeEnd":560,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/services/worker/OpenRouterProvider.ts#L524-L560","documentation":"In OpenRouterProvider.queryOpenRouterMultiTurn, errors thrown by fetchChatCompletion — network failures, DNS errors, connection resets, timeouts, aborts — are passed through classifyOpenRouterError, which wraps the cause into a structured, classified error object before it propagates. This mirrors the Gemini provider pattern and enables consistent retry/messaging logic for OpenRouter requests.","triggerScenarios":"The fetch to OpenRouter's /chat/completions endpoint fails before receiving an HTTP response: network outage, DNS failure, connection refused, TLS error, or the attemptSignal aborted the request.","commonSituations":"Flaky network or VPN drop mid-request; firewall or proxy blocking openrouter.ai; request aborted by user cancellation; long completions exceeding a client-side timeout.","solutions":["Catch the classified error and branch on its classification before deciding to retry","Auto-retry network-classified errors with exponential backoff; never retry user aborts","Check connectivity and proxy/firewall rules for openrouter.ai on persistent failures","Surface the wrapped cause's message for diagnosis when classification is unknown"],"exampleFix":"// before\nconst res = await provider.query(messages);\n// after\ntry {\n  const res = await provider.query(messages);\n} catch (e) {\n  if ((e as OpenRouterError).classification === 'network' && !isAbort(e)) await backoffRetry();\n  throw e;\n}","handlingStrategy":"retry","validationCode":"// Reachability pre-check for OpenRouter\nconst ok = await fetch('https://openrouter.ai/api/v1/models', { method: 'HEAD' })\n  .then(() => true).catch(() => false);\nif (!ok) throw new Error('openrouter.ai unreachable — check network/proxy');","typeGuard":"const isOpenRouterNetworkError = (e: unknown): e is { classification: 'network'; cause: Error } =>\n  typeof e === 'object' && e !== null && (e as any).classification === 'network';","tryCatchPattern":"try {\n  const res = await provider.query(messages);\n} catch (e) {\n  if (isOpenRouterNetworkError(e) && e.cause.name !== 'AbortError') {\n    return backoffRetry(() => provider.query(messages), { retries: 3 });\n  }\n  throw e;\n}","preventionTips":["Retry network-classified errors with exponential backoff","Distinguish aborts from network failures and never retry aborts","Verify proxy/firewall allows openrouter.ai for CI and corporate environments","Set a sane client timeout above expected completion latency to avoid self-inflicted timeouts"],"tags":["network","openrouter","fetch"],"backgroundTag":"network-request-failed","analyzedSha":"d8bc9755e74915e5c3b999181e10a67c889bce2a","analyzedAt":"2026-09-17T16:40:26.182Z","contentChangedAt":"2026-09-17T16:40:26.182Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}