JuliusBrussee/caveman · warning · DOMException

This operation was aborted

Error message

This operation was aborted

What it means

The fetch operation was aborted through its AbortSignal while a proxied HTTPS request was being set up: after the CONNECT tunnel opened, the signal was already aborted, so the tunnel socket is destroyed and a standard DOM-style AbortError ("This operation was aborted") is thrown instead of issuing the request. This mirrors the fetch spec behavior of rejecting with an abort error when a signal fires.

Solutions

  1. Expected behavior: handle the AbortError in your calling code (check `error.name === "AbortError"`) rather than treating it as a network failure.
  2. Increase or remove the timeout that fires the AbortController for slow proxied requests.
  3. Check proxy reachability/latency (HTTP_PROXY/HTTPS_PROXY env) — slow tunnels make aborts more likely.
  4. Abort only after the response has been consumed if you need the body.

Example fix

// before
const res = await fetch(url, { signal: controller.signal });
// after
try {
  const res = await fetch(url, { signal: controller.signal });
} catch (e) {
  if (e.name === "AbortError") return null; // expected cancellation
  throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (signal?.aborted) return; // skip the call entirely before fetching

Type guard

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

Try / catch

try {
  const res = await proxyAwareFetch(url, { signal: controller.signal });
} catch (e) {
  if (isAbortError(e)) return null; // expected cancellation, not a network failure
  throw e;
}

Prevention

When it happens

Trigger: Calling the proxy-aware fetch (via `caveman` CLI network paths) with `init.signal` and aborting the controller while the HTTPS CONNECT tunnel to the proxy is opening, or passing an already-aborted signal that races the tunnel `.then` handler; the abort listener attached to the outbound request also destroys the socket with this error.

Common situations: CLI commands with their own request timeouts aborting in-flight proxied calls, users pressing Ctrl-C, or an upstream caller cancelling a request whose proxy tunnel handshake is slow (common with slow/blocked corporate proxies).

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/5eb32407c991f34c. Report an issue: GitHub.

Appendix: 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 3ee70a1026)