[BUG] Cowork sandbox fails at sdk_install on Windows — VM guest crashes with "connection forcibly closed" (regression SDK 2.1.181 → 2.1.202)
Status Fixed / completed
Reported on v2.1.202
Maintainer reply None cached
Workaround ✓ Mentioned in thread ↓
Activity 6 comments · opened Jul 9, 2026 · closed Jul 28, 2026
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?
- App 1.19367.0, SDK 2.1.202, Windows 11 Pro 10.0.26200
- Last working: SDK 2.1.181, June 21 2026
- Same VM bundle (6d1538ba...) both times — bundle is not the issue
- VM boots and connects, but sdk_install fails 3 seconds in with An existing connection was forcibly closed by the remote host
- Linux claude binary (249.7 MB ELF) exists and is valid; crash appears to be inside the VM guest when running/loading it
- Restarting vmcompute service clears the zombie-VM compounding issue but the root crash repeats
- Logs: %APPDATA%\Claude\logs\cowork_vm_node.log
What Should Happen?
Should not give error
Error Messages/Logs
Failed to start Claude's workspace
failed to send request: failed to write message: An existing connection was forcibly closed by the remote host.
Restarting Claude or your computer sometimes resolves this. If it persists, you can reinstall the workspace or share your debug logs to help us improve.
Steps to Reproduce
- Reinstall "workspace"
- Start claude desktop
- go to cowork and try to do something that requires the sandbox
Claude Model
Sonnet (default)
Is this a regression?
Yes, this worked in a previous version
Last Working Version
SDK 2.1.181 (June 21, 2026)
Claude Code Version
This is a claude desktop issue — App 1.19367.0, bundled SDK 2.1.202
Platform
Claude Pro/Max
Operating System
Windows
Terminal/Shell
PowerShell
Additional Information
_No response_
6 Comments
Additional technical detail
Exact failure sequence (
cowork_vm_node.log)The VM guest closes the connection ~3 seconds after receiving the
sdk_installcommand. Everything before that step succeeds cleanly.Regression confirmed — same VM bundle, different SDK
| Date | SDK Version | Bundle (
6d1538ba...) | Result ||------|------------|----------------------|--------|
| June 21, 2026 | 2.1.181 | unchanged | ✅
vmStatus=ready|| July 9, 2026 | 2.1.202 | unchanged | ❌ crash at
sdk_install|rootfs.vhdx,vmlinuz,initrdare identical between sessions — the VM image is not the causeclaudebinary atclaude-code-vm/2.1.202/claudeis a valid 249.7 MB ELF (magic bytes verified)Secondary bug: zombie VM blocks retries
After the first crash,
vmcompute(Windows Host Compute Service) keeps the crashed VM instance registered. All subsequent boot attempts reuse the same dead instance ID and time out after 60 seconds:Workaround:
Restart-Service vmcompute(elevated) clears the zombie and allows a fresh boot — though the rootsdk_installcrash repeats.System info
vmcompute,HvHost,vmms),HypervisorPresent=TrueUpdate: SDK 2.1.205 also affected (app 1.20186.0.0)
Just upgraded to the latest Claude Desktop release (app
1.20186.0.0) — same crash, same failure point:Updated regression table:
| SDK Version | App Version | Result |
|------------|-------------|--------|
| 2.1.181 | — | ✅ Working (last seen June 21) |
| 2.1.202 | 1.19367.0.0 | ❌ Crashes at sdk_install |
| 2.1.205 | 1.20186.0.0 | ❌ Crashes at sdk_install |
The fix has not shipped yet as of app 1.20186.0.0.
Deep dive: crash pathway identified via rootfs inspection
Mounted and inspected
rootfs.vhdxdirectly to trace the actual failure mechanism.Architecture (corrected)
The file sharing is a two-layer stack:
cowork-svc.execalls HCS APIs withplan9ShareSettings/NetworkPlan9ShareNamesto expose the Windows filesystem to the VM at the hypervisor levelsdk-daemonis itself the FUSE server, bridging the 9P share into a FUSE mount inside the VM (likely/c/). Confirmed via strings in the binary:*fuse.Attr,*fuse.LkIn,mountFd,OnUnmount,tmpsharedsdk-daemonis a statically linked Go binary (15.9 MB) that serves two roles simultaneously: vsock RPC bridge (receives commands from Windows) and FUSE filesystem server (exposes the Windows file tree inside the VM).Crash pathway
The crash in step 6 is the root cause. Whether it's a panic in the FUSE initialization, vsock listener setup, or something else inside the 2.1.202 binary is not visible from the guest side (no crash logs accessible;
/sessionsis empty on disk).Key findings from rootfs
/usr/local/bin/sdk-daemon— 15.9 MB statically linked ELF (Go), last written Jul 9 07:20 (from an earlier failed install of 2.1.202)/usr/local/bin/sandbox-helper— 2.1 MB statically linked ELF (Go)/opt/cowork/cli-wrapperand/opt/cowork/claude-bin/claude— 2.3 MB ELF wrappers, created at Jul 9 07:55 during sdk_install/sessions/— empty; no guest-side crash logs written to disklibfuse.so.2,libfuse3.so.3,fusermount3,mount.fuse39pnet.ko,9pnet_virtio.ko,9p.ko(kernel 6.8.0-124-generic)What would help diagnose further
Guest-side crash logs from
sdk-daemon2.1.202 startup — specifically whatever the Go runtime prints on panic before the process exits. These are currently lost because the vsock closes before they can be transmitted and nothing writes them to disk. If thecoworkd.servicelogged to a file (e.g./var/log/coworkd.log) or if the guest serial console output was captured, the panic message would be visible.Deep Binary Analysis — Root Cause Confirmed
After reverse-engineering the
sdk-daemonbinary directly fromrootfs.vhdx, I can pinpoint the exact crash mechanism. This is a Go runtime initialization ordering bug in the binary itself — not a kernel, VM, CPU feature, or filesystem issue.---
Binary Facts
| Field | Value |
|---|---|
| Path inside rootfs |
/usr/local/bin/sdk-daemon|| Module |
coworkd(subpkgs:coworkd/rpc,coworkd/internal/proxy) || Go version |
go1.24.13— this is NOT a public release; max public is ~1.24.4 || Binary | 15.9 MB ELF64, statically linked, fully stripped |
| BuildID |
123d08c38785ca39ba0303ccca9359e5999d2869|go1.24.13does not exist in the public Go release history. This binary was built with an internal Anthropic Go fork/patch, which is almost certainly where the bug was introduced.---
Crash Reproduction
Only two syscalls execute before the crash — the process never gets a chance to open any file, socket, or vsock connection. The "forcibly closed" error upstream is purely a consequence of the VM process dying before it sends anything.
---
Crash Location
This is inside the Go allocator's size-class dispatch (function at
0x418920). The sequence leading to it:Both
m.mcacheand the global fallback areNULLbecausemallocinit()has never been called at this point.---
Root Cause: Initialization Order Bug
The Go runtime entry (
rt0_goat0x479660) follows this sequence:The stub at
0x47df20is supposed to beosinit. But it routes to a different function:Function
0x43b420does this:0x43b300is a package init task runner. It calls init functions via a dispatch stub at0x47bb60→0x43bfe0, which callsruntime.newobject(VA0x415e80) →mallocgc(VA0x471900) → the allocator at0x418920. The allocator needsm.mcache, whichmallocinit()sets up — butmallocinit()is insideschedinit()at0x442280, which hasn't been called yet.---
Complete Crash Call Chain (from core dump stack)
---
What Changed
In standard Go,
osinit()only callsgetproccount()+getHugePageSize()+osArchInit()— none of which allocate heap memory. Package inits run much later, afterschedinit()/mallocinit().In this binary (
go1.24.13), the function placed in theosinitslot (0x43b420) has been extended to also run package-level init tasks (0x43b300) beforeschedinit()has been called. This means package inits that callnew(T)or any heap allocation crash immediately.This is almost certainly a regression introduced in Anthropic's custom Go build — either a linker change that miscategorized inits, or a runtime patch that moved the init runner too early in
rt0_go.---
What is NOT the Cause
/proc/cpuinfo), crash isSEGV_MAPERRnotSIGILL---
Fix Required
The
sdk-daemonbinary needs to be rebuilt with the correct initialization order. Package-level init functions that allocate heap memory must not run beforeschedinit()/mallocinit()inrt0_go. The smol-bin updater that pushedsdk-daemon2.1.205 delivered a binary that immediately segfaults on startup.Workaround Found — Binary Patch + Persistence Mechanism (Windows)
Following up on the root-cause analysis above (premature package-init call before
mallocinit()/schedinit()inrt0_go), I built and verified a working patch. It fixes the crash, but cowork still cannot fully start due to a second, unrelated bug (filed separately as #77005 — the guest hangs mounting one specific Plan9 share). Documenting the sdk-daemon fix here since it's a complete, verified solution to this issue's root cause, in case it's useful for the eventual real fix or for anyone hitting only this crash.The fix
The crash call site is a single premature
callinstruction. NOP-ing it out removes the premature package-init call; init functions then run normally later viaruntime.main()→doInit(), after the scheduler and allocator are up.Verified: patched binary boots cleanly all the way through networking, RPC handshake, session-user recovery (24/24 recovered), and 3 of 4 attempted Plan9 share mounts — a dramatic improvement over the immediate SIGSEGV the original binary hits on syscall #3.
Why a simple rootfs patch doesn't stick
coworkd's own self-updater compares the running binary's SHA256 against the copy on thesmol-binFAT volume (labeledSDK_DAEMON, mounted fromsmol-bin.x64.vhdxin the app package) on every start, and overwrites+restarts itself if they differ. Sincesmol-bin.x64.vhdxis re-copied from the installed app package into the VM bundle before every VM boot, patching onlyrootfs.vhdxgets silently reverted within seconds of the first start — the updater pulls the broken binary right back in.Full persistence requires patching three places, in order:
smol-bin.x64.vhdxinside the installed app package (...\WindowsApps\Claude_<ver>\app\resources\smol-bin.x64.vhdx) — the ultimate source of truth the updater trusts. Requires taking ownership (takeown/icacls) since it's in a protected AppX/MSIX package directory.rootfs.vhdx's/usr/local/bin/sdk-daemon— so the first boot after this fix is already patched (otherwise the first start still crashes before the updater has a chance to sync from the now-patched smol-bin).coworkd.service— addExecStartPre=/usr/local/bin/sdk-patch, a small idempotent Python script installed at/usr/local/bin/sdk-patchthat re-applies the NOP patch to/usr/local/bin/sdk-daemonbefore every service (re)start. This guards against any future smol-bin update reintroducing the broken binary (e.g. after an app auto-update ships a new sdk-daemon) triggering the crash loop again before the next patch cycle.sdk-patch(installed at/usr/local/bin/sdk-patch, mode 755):coworkd.servicediff:All three edits back up the file they modify (
.baksuffix) before writing, and every patch step verifies the SHA256 of the input before touching it — refuses to run if the binary doesn't match the known-broken hash exactly.Verification
I rebuilt
rootfs.vhdxfrom scratch from the app's own pristinerootfs.vhdx.zstsnapshot (bundled alongside the live one, presumably for the "reinstall workspace" recovery path) and re-applied only the sdk-daemon binary swap — confirming the fix isn't dependent on any of the incidental state that had accumulated in my working rootfs over months of use. Same result both times: clean full boot, pastsdk_install.What's still broken
With this crash fixed, cowork now reaches Plan9 share mounting and hangs indefinitely on one specific share, causing the eventual "connection forcibly closed" / 60s timeout. That's a separate bug, unrelated to this one — filed as #77005 since the mechanism (guest fully up, most shares mount fine, one hangs forever) doesn't match any existing report I could find, including the superficially similar #60875/#66348 (those are guest-never-calls-back-at-all, a different failure mode).
Update: crash appears fixed upstream as of App 1.24012.9.0
Following up on the binary-level root cause and workaround posted above — I hit a timeout again today after Claude Desktop auto-updated (1.20186.1.0 → 1.24012.9.0), so I re-checked whether the same crash had returned.
It hasn't. The
sdk-daemonbinary shipped in this version'ssmol-bin.x64.vhdxis a different build entirely:Different hash, different size, and — most importantly — it boots cleanly with no patch applied. So whatever caused the premature
init_runnercall beforemallocinit()/schedinit()in the old build appears to have been fixed in a subsequentsdk-daemonrebuild.The timeout I hit today was the unrelated Plan9/network-drive hang (#77005), not this crash — confirmed by disconnecting my mapped network drives, which let cowork start successfully.
Given this, the NOP-patch workaround above is no longer needed on current app versions — closing this one out. Thanks for the platform, and see #77005 for the remaining Plan9-share issue if you have mapped network drives.