Linux/WSL2 sandbox: socat proxy bridge starts in the background, so the first network call in a command fails
Summary
In the Linux/WSL2 Bash sandbox, a command whose first action is a network call fails with connection refused on the proxy bridge. Later calls in the same command succeed. Cause: the two socat bridges are started in the background and the user command runs at once, with no wait for the listeners.
Same symptom as #62743 (closed as stale). This report adds the mechanism, measurements, and a suggested fix.
Environment
- Claude Code 2.1.269 (native binary,
latestchannel) - WSL2, NixOS 26.05, kernel 6.18.40.1-microsoft-standard-WSL2
- bubblewrap and socat from nixpkgs
sandbox:{ "enabled": true, "autoAllowBashIfSandboxed": true, "allowUnsandboxedCommands": true, "failIfUnavailable": true }, nonetworkblock
Reproduction
Any Bash tool command that starts with a network call, for example:
git ls-remote https://github.com/<owner>/<repo>.git HEAD
fatal: unable to access 'https://github.com/<owner>/<repo>.git/': Failed to connect to github.com:443 over proxy localhost after 0 ms: Could not connect to server
or
curl -sS -o /dev/null -w '%{http_code}' https://github.com
curl: (7) Failed to connect to localhost:3128 after 0 ms: Could not connect to server
4 of 4 probes failed. ss -ltn as the first action shows no listener on 3128 yet; the same command 100 ms later shows 0.0.0.0:3128 and 0.0.0.0:1080.
Mechanism
The bwrap command runs the shell with this script (from the 2.1.269 binary):
socat TCP-LISTEN:3128,fork,reuseaddr UNIX-CONNECT:/tmp/claude-http-<hex>.sock >/dev/null 2>&1 &
socat TCP-LISTEN:1080,fork,reuseaddr UNIX-CONNECT:/tmp/claude-socks-<hex>.sock >/dev/null 2>&1 &
trap "kill %1 %2 2>/dev/null; exit" EXIT
<seccomp-apply> <shell> -c '<user command>'
Nothing waits for the listen sockets to bind. Measured inside the sandbox with a /dev/tcp poll loop: port 3128 accepts connections 15-30 ms after the inner shell starts, while the inner shell is ready in about 3 ms. So any immediate network call loses the race.
Suggested fix
Wait for the bridge before running the user command, for example a bounded poll in the same script:
for _ in $(seq 1 50); do (exec 3<>/dev/tcp/127.0.0.1/3128) 2>/dev/null && break; sleep 0.01; done
or start socat with a readiness signal. A user-side workaround (a guarded poll in ~/.zshenv) makes 3/3 first calls succeed with a 20-30 ms wait, which confirms the diagnosis.
Related: .gitmodules mask
.gitmodules in the working tree is masked with a /dev/null bind mount. Inside the user namespace the device node cannot be opened (EACCES), so:
git fetchprintswarning: unable to access '.../.gitmodules': Permission deniedtwice (cosmetic), and- libgit2 users fail:
nix flake checkgivesparsing .gitmodules file: failed open - '.../.gitmodules' is locked: Permission denied (libgit2 error code = 2).
Masking with an empty regular file instead of a device node would avoid both.