{"record":{"id":"6bdad22af1dd8b5f","repo":"JuliusBrussee/caveman","slug":"aborterror","errorCode":"AbortError","errorMessage":"This operation was aborted","messagePattern":"This operation was aborted","errorType":"exception","errorClass":"DOMException","httpStatus":null,"severity":"info","filePath":"packages/cli/src/proxy-fetch.ts","lineNumber":241,"sourceCode":"      // Plain HTTP is proxied in absolute form; no tunnel needed.\n      void finish(\n        {\n          host: proxy.hostname,\n          port: portOf(proxy),\n          method,\n          path: url.toString(),\n          headers: { ...headers, host: url.host, ...proxyAuthHeader(proxy) },\n        },\n        sendToProxy,\n      );\n      return;\n    }\n\n    openTunnel(url, proxy, signal)\n      .then((socket) => {\n        if (signal?.aborted) {\n          socket.destroy();\n          throw abortError();\n        }\n        return finish(\n          {\n            method,\n            path: `${url.pathname}${url.search}`,\n            headers: { ...headers, host: url.host },\n            // Node 26 rejects an IP literal as SNI servername; omit it for IP targets.\n            createConnection: () => tlsConnect({ socket, host: url.hostname, servername: isIP(url.hostname) ? undefined : url.hostname }),\n          },\n          httpsRequest,\n        );\n      })\n      .catch(reject);\n  });\n}\n\nasync function bufferedRequestBody(request: Request): Promise<Uint8Array | null> {\n  if ([\"GET\", \"HEAD\"].includes(request.method)) return null;","sourceCodeStart":223,"sourceCodeEnd":259,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/5184b3d11ac6a1acb7d44b9bfaa31698157cff97/packages/cli/src/proxy-fetch.ts#L223-L259","documentation":"Inside sendThroughProxy, after the HTTPS CONNECT tunnel to the proxy succeeds and the destination request is about to be written, the promise chain re-checks the AbortSignal. If the caller's signal was aborted while the tunnel was opening, the socket is destroyed and an AbortError DOMException ('This operation was aborted') is thrown, which propagates to the caller through .catch(reject). This is fetch-spec-compliant cancellation behavior for proxied requests.","triggerScenarios":"Calling the proxy-aware fetch (createProxyAwareFetch) for an https: URL routed through a proxy with an AbortSignal, then calling AbortController.abort() (or the signal's own timeout firing) while openTunnel is still negotiating the CONNECT; the abort lands between tunnel open and the `finish(...)` write, hitting the `signal?.aborted` check at line 239.","commonSituations":"AbortSignal.timeout() expiring because the proxy CONNECT was slow (latency to proxy, proxy auth delays, cold connections); a request watchdog or race in app code canceling the fetch; user navigation canceling in-flight requests; retry wrappers that abort a pending attempt.","solutions":["Don't abort before the timeout expires — increase the AbortSignal.timeout / controller deadline to account for proxy CONNECT overhead (proxy hops add a round trip).","Catch AbortError distinctly and treat it as cancellation, not a server failure: check error.name === 'AbortError' before retrying.","If the abort was unintentional (shared signal, leaked controller), fix the ownership of the signal so only the request's owner aborts it.","If proxies are persistently too slow for your deadline, verify proxy reachability/credentials or bypass the proxy for that host (NO_PROXY / bypass rules)."],"exampleFix":"// before: tight timeout ignores proxy CONNECT latency\nconst res = await proxyFetch(url, { signal: AbortSignal.timeout(1000) });\n\n// after: allow for the extra proxy hop, and handle cancellation explicitly\ntry {\n  const res = await proxyFetch(url, { signal: AbortSignal.timeout(10000) });\n} catch (error) {\n  if (error instanceof Error && error.name === \"AbortError\") {\n    // caller canceled or timed out — don't surface as a server error\n    return null;\n  }\n  throw error;\n}","handlingStrategy":"try-catch","validationCode":"// Before the call: give the tunnel extra budget and verify the signal is live.\nconst controller = new AbortController();\nconst timeout = setTimeout(() => controller.abort(), 15000); // includes CONNECT round trip\nif (controller.signal.aborted) throw new Error(\"signal aborted before request started\");","typeGuard":"function isAbortError(error: unknown): error is DOMException {\n  return error instanceof DOMException\n    ? error.name === \"AbortError\"\n    : error instanceof Error && error.name === \"AbortError\";\n}","tryCatchPattern":"try {\n  const response = await proxyAwareFetch(url, { signal: controller.signal });\n  clearTimeout(timeout);\n  return response;\n} catch (error) {\n  if (isAbortError(error)) {\n    return null; // caller cancellation or tunnel-stage timeout — not a server error\n  }\n  clearTimeout(timeout);\n  throw error;\n}","preventionTips":["Budget timeouts for the extra proxy CONNECT round trip, not just origin latency.","Only abort when you truly mean to cancel; don't share one controller across unrelated requests.","Always branch on error.name === 'AbortError' before retry logic so cancellations aren't retried.","If CONNECT tunnels routinely exceed deadlines, check proxy reachability/auth or add the host to bypass rules."],"tags":["fetch","abort","proxy","timeout","network"],"backgroundTag":"fetch-aborted","analyzedSha":"5184b3d11ac6a1acb7d44b9bfaa31698157cff97","analyzedAt":"2026-08-31T22:10:17.934Z","contentChangedAt":"2026-08-31T22:10:17.934Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}