[BUG] v2.1.113+ on linux-x64 behind HTTP CONNECT proxy: Bun runtime fetch race condition + 3 cascading regressions
[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
- Diff the linux-x64 vs linux-arm64 Bun build pipelines for fetch / proxy tunneling
- Bisect Bun versions between the last known-good Claude Code release and the linux-x64 native build
- 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
showSetupScreens()must notawaitnon-essential network. Render the UI immediately; let telemetry / Statsig / autoupdate run fire-and-forget in the background.- Cap any startup-path non-essential outbound at a tight (≤ 5 s) timeout.
- Provide a single
kill switchenv 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
awaitreturns 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_PROXYfrom 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.
This issue has 11 comments on GitHub. Read the full discussion on GitHub ↗