[BUG] v2.1.113+ on linux-x64 behind HTTP CONNECT proxy: Bun runtime fetch race condition + 3 cascading regressions

Resolved 💬 11 comments Opened Apr 18, 2026 by liuwuyu118 Closed Jun 13, 2026

[BUG] v2.1.113+ on linux-x64 behind HTTP CONNECT proxy: Bun runtime fetch race condition + 3 cascading regressions

TL;DR — root cause

Bun runtime's fetch() in the linux-x64 build hangs intermittently when used through an HTTP CONNECT proxy. This is a race condition: under strace (which slows syscalls via ptrace) it disappears completely. The arm64 build is unaffected. Same proxy works in 0.3s with curl and python urllib. There is no per-binary distribution issue — the official installer and the npm @anthropic-ai/claude-code-linux-x64 optional dep ship the identical ELF (sha256 below).

This single root cause produces several different user-visible failures, documented as Bug #1–#4 below. Bug #4 is the root; Bugs #1–#3 cascade from it (or compound it).

| | x86_64 Linux | arm64 Linux |
|---|---|---|
| 2.1.112 (bundled JS) | 10/10 ✅ | n/a |
| 2.1.114 (Bun native) | 2/10 ✅ (8 TIMEOUT) | 10/10 ✅ |

Same proxy, same ~/.claude/settings.json, same Ubuntu 24.04.3 + glibc 2.39. Only the architecture (and binary) changes.

Workaround: npm i -g @anthropic-ai/claude-code@2.1.112.

---

Environment

| | |
|---|---|
| Claude Code | 2.1.114 (also reproduced on 2.1.113) |
| Distribution | Bun single-file ELF (log paths show /$bunfs/root/src/entrypoints/cli.js) |
| OS | Ubuntu 24.04.3 LTS, kernel 6.8.0-107-generic, glibc 2.39, x86_64 |
| Network | Standard enterprise setup — all egress must traverse a local HTTP CONNECT forward proxy. No direct internet route. |
| Reference machine A | local x86_64 + Claude Code 2.1.112 (bundled JS) — works |
| Reference machine B | aarch64 + Claude Code 2.1.114 (Bun native) — works |

~/.claude/settings.json env block sets HTTPS_PROXY, HTTP_PROXY, ALL_PROXY, NO_PROXY (each in upper- and lowercase), plus OTEL_SDK_DISABLED=true, OTEL_*_EXPORTER=none, DISABLE_AUTOUPDATER=1, CLAUDE_CODE_PROMPT_CACHE_TTL=1h. Verified loaded by debug log: settingsEnv keys: HTTPS_PROXY,HTTP_PROXY,ALL_PROXY,...,OTEL_SDK_DISABLED,....

---

Bug #4 (root cause) — linux-x64 Bun runtime fetch hangs through HTTP CONNECT proxy

Symptom

POST /v1/messages is sent successfully through the CONNECT tunnel. Then 30 seconds pass with zero stream chunks coming back, the client logs Slow first byte: no stream chunk 30.0s after request sent (attempt 1), and the request hangs until the wrapper kills it at 45s. Headless mode reproduces ~75% of the time. TUI hits the same code path during startup and intermittently during long sessions.

Hard evidence

1. Same binary across distribution channels — packaging is innocent:

npm optional dep @anthropic-ai/claude-code-linux-x64 :
  sha256 12bd4b0916deb06be17ffc7b2f0485e140bf00b2db3dcb78469d66723d73c27f
  BuildID 052ef6d8cef1bef39149a31808f3d579db450889
  size   236411520

Official installer  (https://claude.ai/install.sh -- 2.1.114) :
  sha256 12bd4b0916deb06be17ffc7b2f0485e140bf00b2db3dcb78469d66723d73c27f
  BuildID 052ef6d8cef1bef39149a31808f3d579db450889
  size   236411520

2. Network/proxy/upstream are healthy:

| Tool | api.anthropic.com | statsigapi.net | docs.claude.com |
|---|---|---|---|
| curl 8.5.0 (same env, same proxy) | 0.258 / 0.270 / 0.273 s (404) | 0.340 / 0.343 / 0.339 s (403) | 0.501 / 0.598 / 0.420 s (200) |
| Python urllib 3.13 (same env, same proxy) | 0.418 / 0.415 / 0.421 s | 0.333 / 0.337 / 0.338 s | 0.282 / 0.273 / 0.369 s |
| Bun runtime via Claude Code | 30s+ stalls, intermittent |||

tcpdump during a TIMEOUT trial captures a clean CONNECT api.anthropic.com:443 HTTP/1.1\r\nProxy-Connection: Keep-Alive\r\n\r\n request (so this is not issue #14165 "Bun ignores HTTP_PROXY" — it does use the proxy). A successful trial under strace shows TLS ClientHello → ServerHello → application data flowing both directions in the CONNECT tunnel.

3. Race condition (the smoking gun):

Without strace : 2 / 10 OK
Under  strace  : 5 / 5 OK   (ptrace serializes syscalls and adds ~100µs/call)

ptrace overhead reliably masks the bug. Bun uses io_uring on Linux (anon_inode:[io_uring] fds visible in the strace) — the suspected location is the io_uring submission/completion path or the TLS state machine on the CONNECT-tunneled socket.

4. Architecture-selective, not network-selective:

Same proxy, same settings.json, same Ubuntu 24.04.3 + glibc 2.39. The only difference between the working machine and the broken machine is linux-arm64 vs linux-x64 Bun build.

5. Also reproduces on a different multi-core x86_64 box — not a single-vCPU artifact, not specific to the low-power Intel N150 reference machine.

6. No user-side workaround works (each tested 5×, early-aborted on 2 TIMEOUTs):

| Env var | Effect |
|---|---|
| BUN_FEATURE_FLAG_DISABLE_IO_POOL=1 | 2 TIMEOUT (no help) |
| BUN_FEATURE_FLAG_DISABLE_RWF_NONBLOCK=1 | 2 TIMEOUT (no help) |
| BUN_FEATURE_FLAG_FORCE_WAITER_THREAD=1 | 2 OK / 2 TIMEOUT (no change) |
| BUN_FEATURE_FLAG_DISABLE_IPV6=1 | 2 TIMEOUT (no help) |
| BUN_FEATURE_FLAG_DISABLE_DNS_CACHE=1 | 1 OK / 2 TIMEOUT (no help) |
| BUN_CONFIG_MAX_HTTP_REQUESTS=1 | 2 TIMEOUT (no help) |
| BUN_CONFIG_HTTP_RETRY_COUNT=0 | 1 OK / 1 TIMEOUT (no change) |

7. No bundled-JS escape hatch in 2.1.113+ — the npm package's cli-wrapper.cjs only spawnSyncs the same native binary; there is no JS code path and no CLAUDE_CODE_USE_JS_BUILD flag.

Suggested fix direction

  1. Diff the linux-x64 vs linux-arm64 Bun build pipelines for fetch / proxy tunneling
  2. Bisect Bun versions between the last known-good Claude Code release and the linux-x64 native build
  3. Provide an CLAUDE_CODE_USE_JS_BUILD=1 (or equivalent) escape hatch for affected users until the Bun fix lands

Repro

# On any linux-x64 host behind an HTTP CONNECT proxy:
curl -fsSL https://claude.ai/install.sh | bash -s -- 2.1.114
# (or: npm i -g @anthropic-ai/claude-code@2.1.114)

# Configure proxy in ~/.claude/settings.json `env` block (or just export HTTPS_PROXY).

for i in 1 2 3 4 5 6 7 8 9 10; do
  timeout 45 claude -p --dangerously-skip-permissions --output-format stream-json \
    --model claude-haiku-4-5-20251001 'Respond with only the word ok' \
    > /dev/null 2>&1 && echo "$i OK" || echo "$i TIMEOUT"
done

Expect ~75 % TIMEOUT on linux-x64. On linux-arm64 or 2.1.112 expect 0% TIMEOUT.

---

Bug #1 (cascading) — settings.json.env strips HTTP(S)_PROXY and NO_PROXY from spawned subprocess env

Symptom

In ~/.claude/settings.json's env block, set 22 variables. From a Claude session, run Bash tool: printenv. The subprocess inherits 19 of the 22. The 3 missing keys, all in upper- and lowercase = 6 strings, are exactly:

HTTPS_PROXY, HTTP_PROXY, NO_PROXY,
https_proxy, http_proxy, no_proxy

ALL_PROXY / all_proxy are passed through normally. So is every non-proxy variable (CLAUDE_*, ANTHROPIC_*, OTEL_*, COLORTERM, BASH_DEFAULT_TIMEOUT_MS, DISABLE_AUTOUPDATER, etc.).

Evidence

The keys ARE loaded by Claude:

CA certs: Config fallback - globalEnv keys: ,
  settingsEnv keys: ...,HTTPS_PROXY,HTTP_PROXY,ALL_PROXY,
  https_proxy,http_proxy,all_proxy,NO_PROXY,no_proxy,...

But by the time a Bash tool subprocess is spawned, the 6 proxy keys (HTTPS/HTTP/NO, both cases) are gone, while ALL_PROXY survives. Workaround: export the 6 keys in the launcher shell — they then propagate normally — confirming the strip happens specifically in the settings.json → spawn env path, not in the runtime's general inheritance logic.

Suggested fix direction

Audit the env-merge logic between settings.json loading and child_process.spawn. Likely a leftover proxy-agent allow/denylist that filters HTTP/HTTPS/NO_PROXY but not ALL_PROXY. Either remove the filter or document it.

Minimal repro

// ~/.claude/settings.json
{
  "env": {
    "HTTPS_PROXY": "http://example:8080",
    "HTTP_PROXY":  "http://example:8080",
    "NO_PROXY":    "localhost",
    "ALL_PROXY":   "http://example:8080",
    "FOO_BAR":     "visible"
  }
}

In the REPL, ask Claude to run:

printenv | grep -iE '^(https?_proxy|no_proxy|all_proxy|foo_bar)='

Expected: 5 lines. Actual: only ALL_PROXY / all_proxy / FOO_BAR.

---

Bug #2 (cascading) — OTEL_SDK_DISABLED=true does not stop OTEL exporter from opening connections

Symptom

settings.json.env.OTEL_SDK_DISABLED = "true" is loaded (visible in settingsEnv keys). Claude logs [3P telemetry] isTelemetryEnabled=false. But the OTEL exporter still opens TCP connections and times out:

[ERROR] [3P telemetry] OTEL diag error: {
  "message":"Operation timed out.",
  "sourceURL":"/$bunfs/root/src/entrypoints/cli.js",
  "line":"421","column":"107499"
}

Repeats every ~40 s.

ss -tnp while the process is hung shows ≥3 ESTAB TCP connections from the claude pid to the proxy — the OTEL SDK isn't disabled at the network-init layer, only at the application-emit layer.

Suggested fix direction

Short-circuit OTEL exporter creation entirely when OTEL_SDK_DISABLED=true is set, before any socket open. (Currently it appears to create the exporter and only the emit path is gated.)

---

Bug #3 (cascading) — showSetupScreens() blocks for 400+ seconds on non-essential network calls

Symptom

UI sits frozen at the login / setup screen with no feedback for 6 minutes 40 seconds. The --debug log proves it's not a deadlock — it's a await on non-essential outbound HTTPS that times out and is internally retried.

Hard evidence (full log timestamps from one session)

01:31:21.521 [DEBUG] [STARTUP] Running showSetupScreens()...
01:31:21.582 [DEBUG] [Perfetto] initializePerfettoTracing called
01:31:21.582 [DEBUG] [3P telemetry] isTelemetryEnabled=false
01:31:21.588 [DEBUG] DECSTBM: gated
                                                       ← UI frozen here
01:32:01.523 [ERROR] [3P telemetry] OTEL diag error: Operation timed out
                                                       ← still frozen
01:38:02.117 [DEBUG] [STARTUP] showSetupScreens() completed in 400595ms
01:38:02.119 [DEBUG] [API:auth] OAuth token check starting
01:38:42.721 [ERROR] [3P telemetry] OTEL diag error: Operation timed out  ← still retrying after REPL is up

Process state during the hang:

/proc/<pid>/status:    State: S (sleeping), Threads: 6
/proc/<pid>/wchan:     ep_poll
ss -tnp -- pid filter:
  ESTAB ... -> proxy:49998  fd=18
  ESTAB ... -> proxy:49998  fd=17
  ESTAB ... -> proxy:49998  fd=19

3 TCP connections established (so this is not a SYN failure — TCP handshake completed) but the upper layer is waiting on responses that never arrive within the implicit timeout, and the wait is effectively unbounded.

Suggested fix direction

  1. showSetupScreens() must not await non-essential network. Render the UI immediately; let telemetry / Statsig / autoupdate run fire-and-forget in the background.
  2. Cap any startup-path non-essential outbound at a tight (≤ 5 s) timeout.
  3. Provide a single kill switch env var that disables all non-essential outbound at startup, properly honored at the network-init layer (see Bug #2).

---

Why the four bugs likely share one root

  • Bug #4 is the underlying race in Bun's linux-x64 fetch through HTTP CONNECT.
  • Bug #3's intermittency matches Bug #4's intermittency exactly — sometimes the startup await returns fast (TLS race wins), sometimes it doesn't (and you wait 40 s ×N).
  • Bug #2 is the "isTelemetryEnabled=false but exporter still talks" mismatch — a flag layered above the network init, which only ever matters because Bug #4 makes that network init slow.
  • Bug #1 is most plausibly an artifact of Bun's internal "proxy agent injection" — it consumes HTTPS_PROXY/HTTP_PROXY/NO_PROXY from the env, applies them to its own internal HTTP agent, and forgets to put them back when spawning child processes. ALL_PROXY (which Bun's fetch arguably should also honor — see #14165) survives because nothing consumes it.

Fixing Bug #4 would dramatically improve user experience even if the other three remain; fixing all four restores parity with the 2.1.112 / arm64 baseline.

---

Workaround

npm i -g @anthropic-ai/claude-code@2.1.112

The bundled-JS 2.1.112 is 10/10 reliable on the same broken host. That's what we've fallen back to. Affected population: any x86_64 Linux user behind an HTTP CONNECT proxy (very common in enterprise deployments) on Claude Code ≥ 2.1.113.

---

Happy to attach raw strace logs, pcap captures, full --debug logs (showing the 400-second showSetupScreens hang and the 30-second Slow first byte warnings), and /proc/<pid>/{status,wchan,task} snapshots on request.

View original on GitHub ↗

This issue has 11 comments on GitHub. Read the full discussion on GitHub ↗