Linux/WSL2 sandbox: socat proxy bridge starts in the background, so the first network call in a command fails

Status Open
Reported on v2.1.269
Maintainer reply None cached
Activity 0 comments · opened Sep 12, 2026

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, latest channel)
  • 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 }, no network block

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 fetch prints warning: unable to access '.../.gitmodules': Permission denied twice (cosmetic), and
  • libgit2 users fail: nix flake check gives parsing .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.

View original on GitHub ↗