{"record":{"id":"e86bb0525c991db3","repo":"thedotmack/claude-mem","slug":"classifyhttpprovidererror-cause-err-providerlabel-openrouter","errorCode":null,"errorMessage":"classifyHttpProviderError({ cause: err, providerLabel: 'OpenRouter' })","messagePattern":"classifyHttpProviderError\\((.+?)\\)","errorType":"exception","errorClass":"ServerClassifiedProviderError","httpStatus":null,"severity":"error","filePath":"src/server/generation/providers/OpenRouterObservationProvider.ts","lineNumber":88,"sourceCode":"  async generate(\n    context: ServerGenerationContext,\n    signal?: AbortSignal,\n  ): Promise<ServerGenerationResult> {\n    const { prompt, skippedAll } = buildServerGenerationPrompt(context);\n    if (skippedAll) {\n      return {\n        rawText: '<skip_summary reason=\"all_events_private\" />',\n        providerLabel: this.providerLabel,\n        modelId: this.model,\n      };\n    }\n\n    let response: Response;\n    try {\n      response = await this.postChatCompletion(prompt, signal);\n    } catch (networkError) {\n      const err = networkError instanceof Error ? networkError : new Error(String(networkError));\n      throw classifyHttpProviderError({\n        cause: err,\n        providerLabel: 'OpenRouter',\n      });\n    }\n\n    if (!response.ok) {\n      const bodyText = await safeReadBody(response);\n      throw classifyHttpProviderError({\n        status: response.status,\n        bodyText,\n        headers: response.headers,\n        cause: new Error(`OpenRouter API error: ${response.status} - ${bodyText}`),\n        providerLabel: 'OpenRouter',\n      });\n    }\n\n    let data: OpenRouterResponse;\n    try {","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/server/generation/providers/OpenRouterObservationProvider.ts#L70-L106","documentation":"OpenRouterObservationProvider.generate wraps the chat-completions call; any rejection from postChatCompletion (no HTTP response received) is rethrown via classifyHttpProviderError({ cause, providerLabel: 'OpenRouter' }). This is a transport-level failure: the request to openrouter.ai never completed at the HTTP layer.","triggerScenarios":"`postChatCompletion()` rejects: DNS failure, connection refused/reset, TLS error, timeout, or AbortSignal fired — caught at OpenRouterObservationProvider.ts:84-91.","commonSituations":"Outage or brownout of openrouter.ai, DNS/proxy issues, network restrictions in CI, request timeouts on very large completions, VPN interference.","solutions":["Verify connectivity to openrouter.ai from the runtime.","Check proxy/firewall/DNS configuration; test with curl.","Increase the timeout / use a longer-lived AbortSignal for large generations.","Retry with backoff for transient network causes."],"exampleFix":"// before\nconst r = await openRouterProvider.generate(prompt, signal);\n// after\ntry { const r = await openRouterProvider.generate(prompt, signal); }\ncatch (e) { if (e.kind === 'retryable') return retryWithBackoff(); throw e; }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isNetworkError(e: unknown): e is TypeError {\n  return e instanceof TypeError && /fetch|network/i.test(e.message);\n}","tryCatchPattern":"try {\n  result = await openRouterProvider.generate(prompt, signal);\n} catch (e) {\n  if (e.kind === 'retryable') return retryWithBackoff(() => openRouterProvider.generate(prompt, signal));\n  throw e;\n}","preventionTips":["Monitor openrouter.ai uptime; have a fallback provider configured.","Verify egress/proxy access to openrouter.ai in CI and production.","Use AbortSignals with realistic timeouts for large completions.","Distinguish retryable transport errors from fatal ones via error.kind."],"tags":["network","openrouter","fetch","transport-error"],"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"}