JuliusBrussee/caveman · info · DOMException

AbortError

AbortError

Error message

This operation was aborted

What it means

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.

Source

Thrown at packages/cli/src/proxy-fetch.ts:241

      // Plain HTTP is proxied in absolute form; no tunnel needed.
      void finish(
        {
          host: proxy.hostname,
          port: portOf(proxy),
          method,
          path: url.toString(),
          headers: { ...headers, host: url.host, ...proxyAuthHeader(proxy) },
        },
        sendToProxy,
      );
      return;
    }

    openTunnel(url, proxy, signal)
      .then((socket) => {
        if (signal?.aborted) {
          socket.destroy();
          throw abortError();
        }
        return finish(
          {
            method,
            path: `${url.pathname}${url.search}`,
            headers: { ...headers, host: url.host },
            // Node 26 rejects an IP literal as SNI servername; omit it for IP targets.
            createConnection: () => tlsConnect({ socket, host: url.hostname, servername: isIP(url.hostname) ? undefined : url.hostname }),
          },
          httpsRequest,
        );
      })
      .catch(reject);
  });
}

async function bufferedRequestBody(request: Request): Promise<Uint8Array | null> {
  if (["GET", "HEAD"].includes(request.method)) return null;

View on GitHub (pinned to 5184b3d11a)

Solutions

  1. 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).
  2. Catch AbortError distinctly and treat it as cancellation, not a server failure: check error.name === 'AbortError' before retrying.
  3. If the abort was unintentional (shared signal, leaked controller), fix the ownership of the signal so only the request's owner aborts it.
  4. If proxies are persistently too slow for your deadline, verify proxy reachability/credentials or bypass the proxy for that host (NO_PROXY / bypass rules).

Example fix

// before: tight timeout ignores proxy CONNECT latency
const res = await proxyFetch(url, { signal: AbortSignal.timeout(1000) });

// after: allow for the extra proxy hop, and handle cancellation explicitly
try {
  const res = await proxyFetch(url, { signal: AbortSignal.timeout(10000) });
} catch (error) {
  if (error instanceof Error && error.name === "AbortError") {
    // caller canceled or timed out — don't surface as a server error
    return null;
  }
  throw error;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Before the call: give the tunnel extra budget and verify the signal is live.
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 15000); // includes CONNECT round trip
if (controller.signal.aborted) throw new Error("signal aborted before request started");

Type guard

function isAbortError(error: unknown): error is DOMException {
  return error instanceof DOMException
    ? error.name === "AbortError"
    : error instanceof Error && error.name === "AbortError";
}

Try / catch

try {
  const response = await proxyAwareFetch(url, { signal: controller.signal });
  clearTimeout(timeout);
  return response;
} catch (error) {
  if (isAbortError(error)) {
    return null; // caller cancellation or tunnel-stage timeout — not a server error
  }
  clearTimeout(timeout);
  throw error;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@5184b3d11a (2026-08-31). Data as JSON: /api/errors/6bdad22af1dd8b5f. Report an issue: GitHub.