Workspace VM fails when home directory is on a non-default APFS volume (firmlink)

Resolved 💬 3 comments Opened Mar 28, 2026 by rafiki270 Closed May 2, 2026

Summary

The Claude Desktop workspace VM feature fails to start with SDK version not verified when the user's home directory (~) resides on a non-default APFS volume accessed via macOS firmlink. The virtiofs shared directory mount inside the Linux VM cannot read files that physically reside on the secondary volume, even though they are fully accessible on the host through the firmlink.

Environment

Hardware

  • Machine: Mac mini (Mac16,10), Model Z1CF00084B/A
  • Chip: Apple M4 — 10 cores (4P + 6E)
  • Memory: 32 GB
  • Internal storage: 251 GB APFS (disk0)
  • External storage: 2x 1TB Ugreen Thunderbolt 4 NVMe drives in Apple RAID 0 (Stripe), presenting as a single 2TB volume (disk8) connected via Thunderbolt 4 at 40 Gb/s
  • RAID members: disk6s2 + disk7s2
  • RAID name: ProjectsRAID
  • APFS container on RAID: disk4 with 3 volumes:
  • External (disk4s1) — mounted at /System/Volumes/Data/.internal/projects (project files)
  • tmpVolume (disk4s2) — mounted at /System/Volumes/Data/.internal/tmpVolume
  • DevCache (disk4s3) — mounted at /Volumes/DevCache (home directory lives here)
  • Firmware: 13822.61.10

Software

  • macOS: 26.2 (Build 25C56) — macOS Tahoe
  • Claude Desktop: 1.1.9310
  • Claude Code CLI: 2.1.86 — npm version (installed via npm install -g @anthropic-ai/claude-code, NOT the native binary — native binary had issues on this setup)
  • Installed at: /opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/cli.js
  • Claude Code SDK (in VM): 2.1.85
  • Node.js: v25.2.1
  • npm: 11.6.2

Disk layout

/dev/disk0    (internal, 251 GB)
  └─ disk3    APFS Container
     ├─ disk3s1  Macintosh HD          (12.3 GB, system)
     ├─ disk3s5  Data                  (197.4 GB, standard macOS Data volume)
     └─ disk3s6  VM                    (9.7 GB, swap)

/dev/disk6 + /dev/disk7  (external, 2x 1TB Ugreen TB4 NVMe, RAID 0)
  └─ disk8    Apple RAID Stripe → disk4 APFS Container (2.0 TB)
     ├─ disk4s1  External             → /System/Volumes/Data/.internal/projects
     ├─ disk4s2  tmpVolume            → /System/Volumes/Data/.internal/tmpVolume
     └─ disk4s3  DevCache             → /Volumes/DevCache  ← HOME DIRECTORY

Why this setup exists

The Mac mini M4 has a 256GB internal SSD which is insufficient for development work. Two 1TB Thunderbolt 4 NVMe drives are configured as Apple RAID 0 (striped) for maximum throughput. The home directory, projects, and temp volumes are on this external RAID array. This is a reasonable and increasingly common setup for developers with limited internal storage.

Mount configuration (/etc/fstab)

UUID=EAABDDA0-A096-3B9A-BF9A-4E47682C3553 /System/Volumes/Data/.internal/projects apfs rw,noauto
UUID=BE2D07B3-2964-47A0-B11B-0CF7C937A305 /System/Volumes/Data/.internal/tmpVolume apfs rw,noauto

The home directory (/Users/<user>) is mapped to /Volumes/DevCache (disk4s3) via an APFS firmlink. macOS resolves this transparently — all standard tools, applications, and system services work correctly. File paths resolve consistently:

$ stat -f "%d" /Users/<user>/Library/Application\ Support/Claude/claude-code-vm/2.1.85/.verified
16777239  # DevCache device ID

$ stat -f "%d" /System/Volumes/Data/Users/<user>/Library/Application\ Support/Claude/claude-code-vm/2.1.85/.verified
16777239  # Same device — firmlink is transparent on the host

Error

Failed to start Claude's workspace
RPC error: SDK version 2.1.85 not verified at /mnt/.virtiofs-root/shared/Library/Application Support/Claude/claude-code-vm/2.1.85/.verified

The .verified file exists on the host, is readable (permissions -rw-r--r--), and is located at the expected path. The VM guest simply cannot see it through the virtiofs mount.

Root cause analysis

1. VM configuration

From cowork_vm_swift.log:

[VM] Shared directory: /Users/<user>

The Virtualization framework (VZVirtioFileSystemDeviceConfiguration) is configured to share the user's home directory. Inside the VM, this is mounted at /mnt/.virtiofs-root.

2. virtiofs mount succeeds (but contents are invisible)

From coworkd.log (the VM guest daemon):

[coworkd] mounted shared directory at /mnt/.virtiofs-root (root only)

The virtiofs mount itself is created without errors. However, the files on the DevCache volume are not accessible through this mount.

3. SDK install fails immediately

From cowork_vm_node.log:

[postConnect] Installing SDK: subpath=Library/Application Support/Claude/claude-code-vm, version=2.1.85
[error] [VM:start] Startup failed: 'RPC error: SDK version 2.1.85 not verified at /mnt/.virtiofs-root/shared/Library/Application Support/Claude/claude-code-vm/2.1.85/.verified'

The host node process tells the VM to verify the SDK. The VM guest constructs the path /mnt/.virtiofs-root/shared/Library/Application Support/Claude/claude-code-vm/2.1.85/.verified and cannot find or read the file.

4. Why it fails

Apple's Virtualization framework uses VZVirtioFileSystemDeviceConfiguration to create a virtiofs share. When the shared directory path (/Users/<user>) resolves through an APFS firmlink to a different APFS volume on different physical hardware (/Volumes/DevCache on an external Thunderbolt RAID), the virtiofs implementation in the Linux guest kernel cannot properly traverse this mapping. Files that physically reside on the external volume are invisible to the guest, even though they appear at the expected paths on the macOS host.

This is likely because:

  • The Virtualization framework resolves the directory to the DevCache volume when setting up the share
  • The virtiofs protocol exposes the directory tree, but the firmlink boundary between APFS volumes (especially across different physical devices) causes the guest to see an empty or incomplete directory tree
  • The .verified file, the claude binary (227MB), and all other SDK files are inaccessible from within the VM

Note: Other VM operations that do NOT use virtiofs work perfectly — CA certificate installation (transferred via vsock), network configuration, and session disk access all succeed. Only virtiofs-dependent operations fail.

Additional issue: EXDEV during VM bundle download

Before reaching the SDK verification error, there is a separate (now worked-around) issue where the initial VM bundle download fails:

EXDEV: cross-device link not permitted, rename '/var/folders/.../rootfs.img' → '~/Library/Application Support/Claude/vm_bundles/claudevm.bundle/rootfs.img'

The download writes to /var/folders/ (on the internal Data volume, disk3s5) and then attempts rename() to move the file to ~/Library/Application Support/Claude/vm_bundles/ (on external DevCache, disk4s3). rename() cannot operate across different mount points — this is a POSIX limitation, not a macOS bug.

Workaround applied: Created a symlink from ~/Library/Application Support/Claude/vm_bundles/var/tmp/claude-vm-bundles (both on disk3s5). This allows rename() to succeed since source and destination are on the same filesystem. The 10GB rootfs.img downloads and decompresses successfully after this fix.

Proper fix: Use copy + unlink (or Node.js fs.rename with EXDEV fallback to fs.copyFile + fs.unlink) instead of a bare rename() when moving downloaded files to the target directory.

Full startup sequence from logs

Successful steps:

  1. ✅ Bundle download and decompression (after EXDEV workaround)
  2. ✅ Swift VM API loaded
  3. ✅ VM config created (4 CPUs, 4GB RAM, gvisor networking, MAC 2a:57:8a:7e:f3:65)
  4. ✅ VM booted via EFI/GRUB (~160ms)
  5. ✅ Guest vsock connected (~8s)
  6. ✅ Static IP assigned (172.16.10.3/24, gateway 172.16.10.1)
  7. ✅ Guest ready
  8. ✅ 14 CA certificates installed (transferred via vsock, not virtiofs)
  9. ✅ API reachability check passed (status=404 from api.anthropic.com)
  10. SDK install fails — cannot read .verified through virtiofs

coworkd (guest) full startup log:

[coworkd] starting coworkd
[coworkd] console output enabled (writing to /dev/hvc0)
[updater] found smol-bin device: /dev/sda
[updater] mounted smol-bin device /dev/sda at /smol/bin
[updater] checking for updates from /smol/bin
[updater] sdk-daemon update detected (old=8e484a73... new=a6295670...)
[updater] sdk-daemon updated
[updater] sandbox-helper update detected (old=da7c71ae... new=d1b7c599...)
[updater] sandbox-helper updated
[updater] unmounted smol-bin
[coworkd] restarting to apply coworkd update
[coworkd] starting coworkd
[coworkd] console output enabled (writing to /dev/hvc0)
[updater] found smol-bin device: /dev/sda
[updater] mounted smol-bin device /dev/sda at /smol/bin
[updater] checking for updates from /smol/bin
[updater] unmounted smol-bin
[coworkd] checking for Hyper-V host
[coworkd] mounted shared directory at /mnt/.virtiofs-root (root only)
[coworkd] found session disk by label: /dev/nvme1n1
[coworkd] session disk already formatted as ext4
[coworkd] running e2fsck on /dev/nvme1n1
[coworkd] e2fsck clean on /dev/nvme1n1
[coworkd] mounting session disk /dev/nvme1n1 at /sessions
[coworkd] session disk mounted successfully at /sessions
[coworkd] no conda disk attached, skipping
[coworkd] user recovery complete: recovered=0 skipped=1 failed=0
[proxy] generating ephemeral CA certificate (private key in memory only)
[proxy] ephemeral CA generated (expires: 2027-03-28T19:24:57Z)
[proxy] CA certificate installed to system trust store
[proxy] MITM proxy started on /var/run/mitm-proxy.sock
[rpc] connecting to host CID=2 port=51234
[rpc] connected successfully
[coworkd] waiting for IPv4 route
[coworkd] IPv4 route is available
[rpc] ready event sent
[coworkd] checking reachability https://api.anthropic.com/ (timeout=30s)
[coworkd] configuring static IP on enp0s1: 172.16.10.3/24 gateway=172.16.10.1 dns=172.16.10.1
[coworkd] lowered MTU from 1500 to 1400 on enp0s1
[coworkd] static IP configured successfully
[coworkd] installed 14 host CA certificates
[coworkd] reachability check passed (status=404)
[coworkd] connected, waiting for commands
→ SDK install RPC received → fails reading .verified via virtiofs → VM shuts down

Host-side VM swift log:

[VM] startVM called for ~/vm_bundles/claudevm.bundle with memoryGB=4 cpuCount=auto networkMode=gvisor
[VM] Using bundle at ~/vm_bundles/claudevm.bundle
[VM] Creating new session data disk (10GB)
[VM] Disk image created (10GiB, ASIF)
[VM] Found smol-bin image at /Applications/Claude.app/Contents/Resources/smol-bin.arm64.img
[VM] Using gvisor user-mode networking
[VM] Configuration created:
[VM]   - CPUs: 4
[VM]   - Memory: 4GB
[VM]   - Boot: EFI (GRUB)
[VM]   - Rootfs: ~/vm_bundles/claudevm.bundle/rootfs.img
[VM]   - Session data: ~/vm_bundles/claudevm.bundle/sessiondata.img
[VM]   - MAC address: 2a:57:8a:7e:f3:65
[VM]   - Shared directory: /Users/<user>      ← THIS IS THE PROBLEM
[VM] Linux VM started successfully
[VM] Guest connected
[VM] Network status: CONNECTED
[VM] Guest ready
[VM] Installed 14 host CA certificates in guest
→ SDK install attempt → RPC error → VM stopped

Attempted fixes (all failed)

| # | Attempt | Result |
|---|---------|--------|
| 1 | Restart Claude Desktop | Same error |
| 2 | Restart computer | Same error |
| 3 | Reinstall workspace (delete vm_bundles entirely) | Re-downloads successfully (after EXDEV symlink workaround), same SDK verification error |
| 4 | Remove extended attributes (xattr -cr) from .verified and claude binary | Same error — com.apple.provenance was present but not the cause |
| 5 | Write version content to .verified file (echo "2.1.85" > .verified) | Same error — the issue is file visibility, not content |
| 6 | Symlink claude-code-vm directory to /var/tmp/ (on Data volume, same device as /var/folders) | Same error — virtiofs cannot follow symlinks pointing outside the shared directory tree |
| 7 | Delete .auto_reinstall_attempted flag to force fresh install | Triggers fresh download cycle but same SDK error |
| 8 | Toggle chicagoEnabled: false then true in claude_desktop_config.json | Same error |
| 9 | Delete claude-code-vm directory entirely (force re-download of SDK) | Re-created by Claude Desktop with same files, same error |
| 10 | Delete vm_bundles AND claude-code-vm for completely fresh install | Both re-download successfully, same error |

Suggested fixes

For the SDK verification (primary issue):

  1. Transfer SDK files via vsock instead of virtiofs — CA certificates are already transferred this way successfully. The SDK binary and .verified file could use the same vsock-based transfer mechanism, bypassing virtiofs entirely. This would make the workspace feature work regardless of home directory location.
  1. Resolve the real path before configuring the virtiofs share — Use realpath() or equivalent to fully resolve the firmlink before passing the directory to VZVirtioFileSystemDeviceConfiguration. Alternatively, try using the resolved path /Volumes/DevCache/Users/<user> as the shared directory instead of /Users/<user>.
  1. Copy SDK files into the VM image — Instead of expecting the guest to read SDK files from virtiofs, copy them into the sessiondata.img or inject them during the VM boot process.
  1. Verify virtiofs accessibility from the guest before failing — Add a diagnostic step that checks if the guest can list the virtiofs mount contents (e.g., ls /mnt/.virtiofs-root/shared/), so the error message can distinguish between "file doesn't exist" and "virtiofs mount is empty/broken". This would make the error message actionable for users.

For the EXDEV download issue:

  1. Add EXDEV fallback in the download/rename logic — When rename() fails with EXDEV, fall back to fs.copyFile() + fs.unlink(). This is a standard Node.js pattern for handling cross-device moves:
try {
  await fs.rename(src, dst);
} catch (err) {
  if (err.code === 'EXDEV') {
    await fs.copyFile(src, dst);
    await fs.unlink(src);
  } else throw err;
}

Reproduction

This will reproduce on any macOS system where the user's home directory resides on a non-default APFS volume, including:

  • External Thunderbolt/USB storage with APFS volumes (this case)
  • Apple RAID arrays with home directories
  • Relocated home directories for storage management on machines with small internal SSDs
  • Enterprise setups with network or external home directories
  • Any configuration where stat -f "%d" ~ returns a different device than stat -f "%d" /System/Volumes/Data

Steps to reproduce:

  1. Have a macOS system where the home directory is on a non-default APFS volume (external drive, RAID, etc.)
  2. Enable workspace in Claude Desktop (chicagoEnabled: true)
  3. Attempt to start a workspace
  4. Observe the EXDEV error on first attempt (if no workaround)
  5. After applying the symlink workaround for EXDEV, observe the SDK verification error

Log file locations

All logs are in ~/Library/Logs/Claude/:

  • cowork_vm_node.log — Host-side VM orchestration (Node.js process)
  • cowork_vm_swift.log — Virtualization framework events (Swift/macOS)
  • coworkd.log — Guest daemon output (Linux VM, coworkd binary)
  • vzgvisor.log — gvisor user-mode networking
  • main.log �� Claude Desktop main process

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗