[Bug] Bash tool children on Windows can't unlink AF_UNIX sockets (error 1920, path poisoned) — breaks unix-socket daemons
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
On Windows, any process launched from inside a Claude Code session's process tree (the Bash
tool and its descendants) exhibits a broken AF_UNIX socket lifecycle:
- The socket file is not auto-removed when the socket closes (on healthy Windows it is).
- Deleting it fails with error 1920 (
ERROR_CANT_ACCESS_FILE, "The file cannot be
accessed by the system").
- The name becomes poisoned:
Test-Path/statsay the file doesn't exist, yet creating a
directory over the same path fails with "already exists". The path is unusable from then on,
and the poisoning outlives the session, the Claude Code version, and reboots.
The same operations from a plain user terminal on the same machine, same user, same binaries,
work perfectly (socket file auto-removed at close). The defect follows the Claude Code process
tree, not the machine.
Two further manifestations observed in the same contexts:
- Named-pipe access degradation: processes spawned from the Claude Code tree could no
longer even open a healthy, user-started daemon's named pipe, while a plain terminal
opened it fine. The client then tried to auto-start a second daemon, which self-poisoned.
- Unkillable children: one poisoned daemon spawned from the Claude Code tree became a
zombie holding its state DB open; a normal kill failed and it required an elevated
taskkill (one instance resisted even that until reboot).
Practical consequence: any software that binds AF_UNIX sockets at fixed paths is permanently
broken when started from Claude Code. Docker Sandboxes' sandboxd daemon is the concrete
victim: started via a Claude-spawned sbx command it self-poisons its socket paths on first
start and can never start again, requiring state surgery. We lost several days to this
before isolating the cause, because it presents as the victim software's bug.
What Should Happen?
Child processes of the Bash tool should have normal Windows AF_UNIX socket semantics —
identical to running the same command in a plain terminal:
- On socket close, Windows auto-removes the socket file (
closed; file exists: False). - If a socket file does linger,
DeleteFile/Remove-Itemon it succeeds. - After deletion, the path name is immediately reusable.
No handle to a child's socket should remain alive anywhere in the harness process tree after
the child has closed it and exited.
Error Messages/Logs
Probe output from inside a Claude Code session (2.1.206, Bash tool, 2026-07-10; identical
output captured on 2.1.198 earlier the same day, freshly restarted session in each case):
probe path: C:\Users\<user>\AppData\Local\Temp\afunix-probe.sock
NOTE: leftover probe file from a previous run is still undeletable; using a unique name.
probe path: C:\Users\<user>\AppData\Local\Temp\afunix-probe-3cd47e20.sock
bound+listening; file exists: True
closed; file exists: True
UNLINK FAILED: The file cannot be accessed by the system. : 'C:\Users\<user>\AppData\Local\Temp\afunix-probe-3cd47e20.sock'.
MKDIR OVER NAME FAILED: An item with the specified name C:\Users\<user>\AppData\Local\Temp\afunix-probe-3cd47e20.sock already exists.
RESULT: BROKEN - AF_UNIX socket files become undeletable ghosts on this machine.
Note the first line: the ghost left by probe runs from previous days and previous Claude
Code versions is still undeletable.
The same script run moments apart from a plain terminal on the same machine:
bound+listening; file exists: True
closed; file exists: False
socket file auto-removed on close (healthy Windows behavior)
RESULT: healthy - AF_UNIX socket cleanup works normally.
Downstream victim symptom (Docker Sandboxes daemon, once poisoned): start/stop loop in itsdaemon.log, database already in use from second-daemon attempts, ghost .sock files in its
state directory that cannot be deleted.
Steps to Reproduce
- Save the following as
afunix_probe.ps1(requires PowerShell 7+ for
UnixDomainSocketEndPoint):
``powershell``
$p = Join-Path ([System.IO.Path]::GetTempPath()) 'afunix-probe.sock'
if (Test-Path $p) { Remove-Item $p -Force -ErrorAction SilentlyContinue }
$ep = [System.Net.Sockets.UnixDomainSocketEndPoint]::new($p)
$s = [System.Net.Sockets.Socket]::new('Unix', 'Stream', 'Unspecified')
$s.Bind($ep); $s.Listen(1)
"bound; file exists: $(Test-Path $p)"
$s.Close(); $s.Dispose()
"closed; file exists: $(Test-Path $p)" # healthy Windows: False (auto-removed)
if (Test-Path $p) {
try { Remove-Item $p -Force -ErrorAction Stop; "unlink OK" }
catch { "UNLINK FAILED: $($_.Exception.Message)" } # broken: error 1920
try { New-Item -ItemType Directory -Path $p -ErrorAction Stop | Out-Null; "name clean" }
catch { "NAME POISONED: $($_.Exception.Message)" } # broken: 'already exists'
}
- Run it from a plain user terminal:
pwsh -NoProfile -File afunix_probe.ps1
→ closed; file exists: False (auto-removed, healthy).
- Start a Claude Code session on the same machine and ask Claude to run the same command
via its Bash tool:
pwsh -NoProfile -File afunix_probe.ps1
→ file still exists after close, UNLINK FAILED ... The file cannot be accessed by the (error 1920), then the mkdir-over-name check fails.
system
- Observe that each run from step 3 leaves one undeletable ghost file in
%TEMP%, which
remains undeletable in later sessions and after reboot.
The context comparison is the point of the repro: identical script, identical user, identical
machine — only the ancestry of the process differs.
Claude Model
Fable 5
Is this a regression?
No, this never worked
Last Working Version
_No response_
Claude Code Version
2.1.206 (Claude Code)
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
PowerShell
Additional Information
Environment
| | |
|---|---|
| OS | Windows 11 Pro 25H2, build 10.0.26200.8737 |
| Claude Code | 2.1.206 (latest at time of writing) — reproduced live 2026-07-10; also reproduced identically on 2.1.198 the same day (native installer) |
| Bash tool shell | Git Bash (MSYS) |
| Probe runtime | PowerShell 7 (.NET UnixDomainSocketEndPoint); also reproduced by a Go binary (Docker's sandboxd) |
| EDR | SentinelOne (ruled out — below) |
When it was first observed — honest scoping: first observed 2026-07-06. We initially
read this as a regression (sbx worked fine on 2026-07-02), but on re-examination the 07-02
sessions most likely had the daemon started from a user terminal, so the AF_UNIX cleanup
path was probably never exercised from the Claude Code process tree before 07-06. We
therefore cannot distinguish between (a) a regression in early July 2026 and (b) a
long-standing bug that a Claude-spawned daemon start first exposed on 07-06. What we did
verify: no machine-level change explains the 07-06 breakage (see below), and the bug
reproduces on 2.1.198 and 2.1.206.
What we ruled out (so you don't have to ask):
- SentinelOne EDR — exonerated. Reproduced with Device Control disabled + reboot, all four
S1 kernel drivers in Stopped state, and no S1 DLL injected into the probing process
(verified via loaded-module list). Also reproduced identically before and after an S1
upgrade (22.3 → 25.2.6.442).
- Windows June cumulative update — the same OS build (and
afd.sys) that shows the broken
behavior from the Claude Code tree shows fully healthy AF_UNIX cleanup from a plain terminal,
today. An OS-level defect would not select on process ancestry.
- Google Drive File Stream, filesystem minifilters — all loaded minifilters were
Microsoft's; Drive FS driver predates the onset by a year.
- "The machine is broken" — no: the probe from a plain terminal is fully healthy. Only the
Claude Code process tree exhibits the defect.
Hypothesis: on Windows, an AF_UNIX socket file can only be unlinked once no handles to the
underlying socket remain, and healthy closure removes the file automatically. The observed
behavior — file lingers after Close()+Dispose() in the child, unlink returns 1920 — is
exactly what handle duplication/inheritance into a longer-lived ancestor process would
produce: some process in the Claude Code harness tree is holding a duplicated handle to
sockets its descendants create, keeping the kernel object alive after the child has closed it.
The likely home for that is the harness's Bash-tool spawn path (handle inheritance flags, job
objects, or the shell-snapshot mechanism) — #68858 below independently shows stray handle
inheritance in that path. Whether this arrived with an early-July update or has been present
longer is unknown (see the scoping note above). One wrinkle we can't fully explain under a pure
live-handle theory: ghost files remain undeletable across reboots (we expect chkdsk
would be needed to reclaim them). We note it as-is.
Possibly related open issues (checked 2026-07-10 — no existing issue covers this exact
bug):
- #68858 "Windows: uv-based (uvx)
MCP servers fail to start — child uv inherits a handle into its own build temp dir" —
independent confirmation of stray handle inheritance in the Windows spawn path, the same
mechanism hypothesized above; ours is the mirror image (a lingering duplicated handle keeps
the child's socket alive instead of blocking its dir).
- #76353 "Bash tool leaks orphaned
child processes on Windows timeout" — leaked/held child handles would produce exactly the
socket-lifetime symptoms here (our unkillable-child manifestation is also an orphaned child).
- #73543 "Local stdio MCP server
fails with 'access denied for socket file' ... on Windows" — different manifestation
(spawn-time access denial vs cleanup failure), opened 2026-07-02, a few days before we first
observed ours.
Workarounds in use: all daemon lifecycle operations run from the user's own terminal,
never via Claude's Bash; Claude-spawned sbx commands limited to ones that cannot auto-start
the daemon; ghost files left in %TEMP% and the sbx state dir pending chkdsk.
Artifacts available on request: the full probe script with verdict logic
(afunix_probe.ps1); timestamps of good/bad sessions bracketing the onset window;
process/module inventories from the EDR-exoneration runs; sandboxd daemon logs showing the
poisoned-restart loop.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗