{"record":{"id":"0e5b0ed0d34c6b0e","repo":"thedotmack/claude-mem","slug":"classifygeminierror-cause-err","errorCode":null,"errorMessage":"classifyGeminiError({ cause: err })","messagePattern":"classifyGeminiError\\((.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/services/worker/GeminiProvider.ts","lineNumber":351,"sourceCode":"      turns: history.length,\n      totalChars\n    });\n\n    const url = `${GEMINI_API_URL}/${model}:generateContent?key=${apiKey}`;\n\n    await enforceRateLimitForModel(model, rateLimitingEnabled);\n\n    // Track request-id (best-effort dedup) across retries.\n    let priorRequestId: string | null = null;\n\n    const data = await withRetry<GeminiResponse>(async (attemptSignal) => {\n      let response: Response;\n      try {\n        response = await this.fetchGenerateContent(url, contents, priorRequestId, attemptSignal);\n      } catch (networkError: unknown) {\n        // Network failures, aborts, DNS, etc.\n        const err = networkError instanceof Error ? networkError : new Error(String(networkError));\n        throw classifyGeminiError({\n          cause: err,\n        });\n      }\n\n      const requestId = response.headers.get('x-goog-request-id') ?? response.headers.get('x-request-id');\n      if (requestId) {\n        priorRequestId = requestId;\n      } else {\n        logger.debug('SDK', 'Gemini response missing request-id header; retry dedup is best-effort');\n      }\n\n      if (!response.ok) {\n        const errorBody = await response.text();\n        throw classifyGeminiError({\n          status: response.status,\n          bodyText: errorBody,\n          headers: response.headers,\n          cause: new Error(`Gemini API error (status ${response.status})`),","sourceCodeStart":333,"sourceCodeEnd":369,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/services/worker/GeminiProvider.ts#L333-L369","documentation":"In GeminiProvider.queryGeminiMultiTurn, any failure thrown by fetchGenerateContent — DNS resolution errors, connection resets, TLS failures, timeouts, or AbortSignal aborts — is wrapped via classifyGeminiError into a structured error object before propagating to the caller. classifyGeminiError inspects the cause and attaches a classification (e.g. network vs abort vs rate limit) used for retry and user-facing messaging.","triggerScenarios":"The fetch to the Gemini generateContent endpoint fails before an HTTP response exists: network outage, DNS failure, connection refused/reset, request aborted via attemptSignal, or fetch throwing for a malformed URL/proxy issue.","commonSituations":"Offline or flaky network during a long multi-turn session; corporate proxy blocking generativelanguage.googleapis.com; user cancellation propagating an abort; VPN drops mid-request.","solutions":["Catch the classified error in the caller and check its classification before retrying","Retry network-classified failures with backoff; respect any retry-after data attached by the classifier","Check connectivity/proxy settings (HTTPS_PROXY) for persistent connection failures","Distinguish abort errors (user intent) from network errors and do not auto-retry aborts"],"exampleFix":"// before\nconst text = await provider.query(messages);\n// after\ntry {\n  const text = await provider.query(messages);\n} catch (e) {\n  if ((e as GeminiError).classification === 'network') await backoffRetry(() => provider.query(messages));\n  else throw e;\n}","handlingStrategy":"retry","validationCode":"// Before calling, verify connectivity and endpoint reachability\nif (typeof navigator !== 'undefined' && navigator.onLine === false) {\n  throw new Error('offline: skip Gemini query until network returns');\n}","typeGuard":"const isNetworkClassified = (e: unknown): e is { classification: 'network'; cause: Error } =>\n  typeof e === 'object' && e !== null && (e as any).classification === 'network';","tryCatchPattern":"try {\n  const text = await provider.query(messages);\n} catch (e) {\n  if (isNetworkClassified(e) && !isAbortError(e.cause)) {\n    return backoffRetry(() => provider.query(messages), { retries: 3 });\n  }\n  throw e;\n}","preventionTips":["Retry with exponential backoff for network-classified failures","Never retry AbortError — that is user intent","Check HTTPS_PROXY / VPN / firewall rules for generativelanguage.googleapis.com","Wrap long multi-turn sessions with a watchdog that reconnects on transient failures"],"tags":["network","gemini","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"}