[BUG] Cowork on Windows: bash sandbox can create files on mounted host folder but unlink is denied — breaks all git write operations
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?
In Cowork on Windows, the bash sandbox can mount a selected host folder and perform read and create-write operations against it, but the unlink syscall is denied at the FUSE/mount layer. POSIX permissions and chmod have no effect — unlink is rejected regardless. sudo is not available (no-new-privs is set on the sandbox).
This is distinct from the reported stale-index.lock issue (#28546), which attributes the symptom to Windows file-handle latency. The behavior here is structural: a brand-new file created microseconds before the unlink attempt, on a path the sandbox just wrote, still fails to unlink. There is no race window where it works.
The practical effect is that all git write operations from the bash sandbox on a mounted Windows folder fail. git add partially succeeds but emits "unable to unlink" warnings on temp objects and .git/index.lock. git commit then fails because the prior .git/index.lock cannot be removed. Subsequent rm -f .git/index.lock from the bash sandbox also fails. Recovery requires the user to open PowerShell and clean up .git/ manually.
This makes the documented "Cowork can manage version control" workflow non-functional on Windows when the repo lives on a mounted host folder.
What Should Happen?
The bash sandbox should be able to perform unlink on paths under the mounted host folder, given that it can already create and write files there. Without unlink, git operations and any other workflow that uses temp/lock files cannot complete inside the sandbox.
If the unlink restriction is a deliberate security measure (preventing the sandbox from deleting host files), it should be documented, and the project-level workflow guidance should be updated to clarify that Cowork on Windows cannot perform git write operations on a mounted host folder.
Error Messages/Logs
$ git commit -m "test"
fatal: Unable to create '/sessions/<session>/mnt/<folder>/.git/index.lock': File exists.
Another git process seems to be running in this repository, e.g.
an editor opened by 'git commit'. ...
$ rm -f .git/index.lock
rm: cannot remove '.git/index.lock': Operation not permitted
Control test demonstrating the structural unlink denial:
$ cd /tmp && touch foo && rm foo && echo "tmp ok"
tmp ok
$ cd /sessions/<session>/mnt/<folder> && touch foo && rm foo
rm: cannot remove 'foo': Operation not permitted
Same file, freshly created microseconds before the unlink. POSIX permissions and chmod have no effect.
Steps to Reproduce
On Windows 11 (any recent build with WSL2 / Hyper-V Platform installed), open Cowork.
Select a folder that is a git repo (any folder under C:\Users\<you>\... works — does not need to be in OneDrive or Downloads, which have their own known mount issues).
Start a session and run the following control test in the bash tool:
uname -a
cd /tmp && touch foo && rm foo && echo "tmp ok"
cd /sessions/<session>/mnt/<folder> && touch foo && rm foo && echo "mnt ok"
The first rm succeeds. The second fails with rm: cannot remove 'foo': Operation not permitted.
Now reproduce the git failure:
cd /sessions/<session>/mnt/<folder>
echo "# test" >> .gitignore
git add .gitignore
git commit -m "test"
git add will emit unlink warnings; git commit will fail with Unable to create '.git/index.lock': File exists.
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.121 (Claude Code)
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
PowerShell
Additional Information
This is a Cowork-on-Windows bug. The form fields lean toward the Claude Code CLI but the Cowork sandbox runs Claude Code under the hood, so the same repo is the right place — see prior Cowork-tagged issues like #28546, #25096, #25293, #41710, #42520, #45433, #50873.
Related-but-distinct issues searched and ruled out as duplicates:
- #28546 (stale .git/index.lock on Windows) — closest match, but reporter attributes the cause to Windows file-handle latency. The control test in this report (
touch foo && rm fooon a brand-new file in the mount) rules that out: there's no race window, the unlink is structurally denied. - #11005 (closed NOT_PLANNED) — Linux/macOS scope, different platform.
- #41710, #42520, #45433, #50873 — FUSE cache coherency / stale content issues. Different symptom (read returns wrong data); this report is a write-side issue (unlink denied).
- #25xxx series — virtiofs/Plan9 mount-fails-to-establish issues. The mount in this report establishes successfully; the issue is what's allowed once it's mounted.
The repo I tested against is on local disk under the user profile (NOT OneDrive, NOT Downloads), so the special-folder mount issues don't apply.
Repro environment: Windows 11, WSL2 newly installed (Cowork's bash sandbox booted successfully on first session after the install), Cowork session in a fresh chat, no other modifications.
14 Comments
Found 1 possible duplicate issue:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
Not a duplicate of #53396. Both involve "Operation not permitted" on
rm, but they're separate bugs:claude-code-vm/<version>/node_modules/) on Windows ARM64. The reporter attributes it to NTFS file-handle semantics — another process holding open handles without FILE_SHARE_DELETE.touch foo && rm fooon a freshly-created file in/tmpsucceeds; same test on the mount fails) rules out the file-handle theory: no other process has ever opened the file. The denial is structural at the FUSE/mount-policy layer, not racy.The user-facing impact is also different: #53396 is log spam during SDK install, this is a complete block on git write workflows in mounted user folders. Different path, different cause, different mitigation.
I appear to have the same problem via cowork on macos, with failing unlinking in the sandbox appearing to be the root cause of the issue. Thanks for fixing this!
Claude-Cowork-generated summary of the issue on my side (Desktop app version: (1.5354.0 (9a9e3d) 2026-04-29T01:14:34.000Z):
Seeing the very same issue described above. Git leaves orphan files when using Cowork on macOS.
Independently hit this as well (Windows 11, local disk). Confirming the structural unlink denial. Cowork bash could not remove
.git/index.lockno matter how much we huffed and puffed to blow this house down. Ended up using PowerShell locally to clean up index.lock between every Cowork to git mediated operation. Every. Operation. No keyboards were destroyed in the repeated attempts to explore workarounds.When using Claude Code (Desktop and CLI) I did not hit this issue. Bummer, as I'm enjoying Cowork Desktop and am excited to see this resolved.
I have the diagnostic data Claude captured/created during this. Eg. file size deltas vs HEAD, SHA256 hashes, session timing, totally down to share all 10KB of this data if needed.
Another workaround pattern, complementary to @Snow333 's: rather than cleaning up index.lock after the fact, we route all git writes through a PowerShell wrapper on the host so the sandbox's unlink denial never trips. Works reliably across commit / branch / stash operations. Script available if helpful.
I have the exact same issue
TL;DR
The root cause of this issue is that Cowork's bindfs mount is configured with the
--delete-denyoption, which is bindfs's documented policy for makingunlink()andrmdir()returnEPERMon the mount, resulting in a denied unlink.Removing that flag fixes this issue, but
--delete-denyis probably a deliberate anti-data-loss safeguard (preventing the sandbox from deleting host files), so the safety implications of removing it would need to be reviewed. A narrower allowlist could meet the same intent without breaking git and standard atomic-write workflows. The permission-asymmetry test below isolates the configuration exactly. Detailed evidence and proposed fix follow.---
_Claude Assisted Bug Report_
Reproducing this on a separate Windows install. We have direct evidence Cowork's mount is bindfs with
--delete-denyenabled, which is the root cause of this issue. While testing, we also reproduced #42520's read-side cache pathology on the same bindfs mount in the same session — independent bug, independent fix, but co-located in the same Cowork mount-setup code.Environment: Cowork in the Claude desktop app on Windows. The bash sandbox reports
claude --version=2.1.138 (Claude Code)(newer than #55206's 2.1.121 — bug persists through CC updates). Sandbox is Ubuntu 22.04.5 LTS, kernel 6.8.0-106-generic. bindfs 1.14.7 (current stable, no upstream bugs in this area — this is a Cowork configuration issue).The smoking gun: permission asymmetry matches bindfs
--delete-denyexactlybindfs's documented
--delete-denyoption disallows file deletion while leaving all other operations untouched. We ran every destructive syscall on a probe directory in our mounted workspace folder:| Operation | Result | bindfs flag implicated |
|---|---|---|
|
mkdir,touch,write,chmod,truncate,utimes,link,symlink| ✅ all succeed | — ||
mv(rename) | ✅ succeeds |--rename-denyOFF ||
unlink| ❌ EPERM |--delete-denyON ||
rmdir| ❌ EPERM |--delete-denyON |straceconfirmed the failure isunlinkat(...) = -1 EPERMat the kernel syscall level, not a higher-level interpretation. Python reportserrno=1 (EPERM). Read-only mounts returnEROFSinstead ofEPERM, separating the RO-mount semantics from the--delete-denypolicy.Direct evidence bindfs is in use
/usr/bin/bindfsin the sandbox. (Why would it be there if it weren't being used?)/proc/self/mountinfoshows/mnt/.virtiofs-root/shared/c/Users/<user>/<folder>as the source of the FUSE mount at/sessions/<id>/mnt/<folder>— exactly the NTFS → virtiofs (host↔VM) → bindfs (FUSE) → sandbox stack #42520 reverse-engineered./sys/fs/fuse/connections/(five connections for five mounts) — bindfs's signature, not virtiofsd's.user_id=0,group_id=0,default_permissions,allow_other.chmod 777→0700,chmod 644→0700, etc.; same chmod on tmpfs from the same process works normally). bindfs has a--permsoption that does exactly this. Confirms the bindfs layer is actively rewriting metadata, not passively forwarding.Direct reproduction of #42520's read-side cache pathology in the same session
Same mount, same shell session. Coordinated test with PowerShell on the host:
_cache_test.txt. Sandbox view: size=100, sha256=d82c6aa1.... Correct."host_wrote_short". Host-sideGet-Item.Length=16,Get-FileHash=f434f899.... File is genuinely 16 bytes on NTFS.size=100(the old sandbox-originated size),sha256=d3853c93...(= 16 host bytes + 84 null bytes). This is exactly the pathology #42520 documents: FUSE size cache stuck at the last sandbox-originated value, host content visible up to that size with null-padding beyond.size=100, sha256=d3853c93.... No natural cache expiration observed within 111 seconds.ls -la <file>, freshopen()+read(),ls -laof parent dir, sandbox-sidetouch(utimensat). The cache is opaque to userspace invalidation hints from the sandbox side.open('wb')(truncate-and-write) correctly updates the cached size;open('r+b').write()(partial overwrite without truncate) does not update the cached size even after the bytes change.open('wb').write(b'sandbox_overwrite'), host-sideGet-FileHashconfirmed the file on NTFS matched the sandbox's last write exactly. So the bug is purely on the read-side invalidation path — writes propagate fine.Connection to the read-side issue cluster
The write-side issues (this report and #55206 itself) and the read-side cache-coherency cluster (#38993, #41710, #42520, #45433, #50873) are independent bugs with independent root causes — but they are co-located: both surface from Cowork's bindfs mount on Windows, reproducible simultaneously on the same mount instance. They likely land on the same engineering team and could plausibly share a single triage thread, but two distinct fixes are needed:
--delete-denyis ON, causing the write-side EPERM symptoms in this issue and #55206. Fix is well-defined (see below).Persistence
EPERM behavior survives: Cowork app restart via Task Manager, "Workspace needs to be reinstalled" prompt, full Windows reboot, fresh Cowork session pointed at a brand-new empty folder created moments earlier in PowerShell, and two distinct sandbox user identities (
intelligent-compassionate-wright,gallant-sweet-babbage) across separate sessions. Not transient state, not per-folder, not per-user.Workaround
For git: plumbing-path commit pipeline avoiding
.git/index.lockby working on an alternate index in tmpfs (which is exempt from EPERM):Produces structurally correct commits. Stale
.git/index.lock,.git/HEAD.lock, and orphantmp_obj_*blobs accumulate (cleanup via PowerShellRemove-Item+git gc --prune=now). The fact that tmpfs is exempt from EPERM is itself evidence the issue is at the bindfs layer, not virtiofs or anything below.Suggested fix
Remove
--delete-denyfrom Cowork's bindfs invocation. That single change resolves this issue and #55206. If--delete-denywas added deliberately as an anti-data-loss safeguard ("don't let the sandbox delete user files"), the same intent could be met by a narrower allowlist that permits.git/lock files andtempfile+rename+unlinkatomic-write patterns. Either way, removing the blanket deny is necessary for git and most standard write workflows to function inside the sandbox.The read-side cluster requires a separate fix that this report doesn't fully characterize. The exact root cause could be excessive FUSE
attr_timeout/entry_timeoutsettings, missing invalidation propagation from virtiofs through bindfs to the kernel, bindfs itself ignoring or mis-handling invalidation events, or a deeper architectural issue with bindfs-over-virtiofs cache coherency when the underlying file is written from outside the bindfs instance. The observed asymmetric invalidation on sandbox writes (truncate+write does invalidate, partial overwrite does not) is a clue that may rule out "just tune the timeouts" as a complete fix. Reverse-engineering the exact mechanism requires access to Cowork-internal mount-setup code.Confirming on macOS (Darwin 24.2.0, current Claude desktop) — the bindfs
--delete-denyroot cause @tisalvadores identified isn't Windows-specific; the same mount policy is active on the macOS sandbox.Reproduction:
Folder is a plain local path under
/Users/<u>/dev/..., not iCloud-synced, no file provider, nochflags, Full Disk Access granted to Claude. Cowork's bash sandbox cantouchthe file but notrmit.Cascading git failures observed:
git add→fatal: Unable to write new index file;git pull→warning: unable to unlink '.git/objects/maintenance.lock';git pushrejected non-fast-forward because fetch couldn't update refs. Cowork's own Read/Write/Edit tools succeed (they don't go via the bash mount path).Persistence ruled out on macOS across: Claude desktop quit + restart, full Mac reboot, removing + re-granting Full Disk Access for Claude, and a fresh Cowork session pointed at the same folder (different probe filename, identical EPERM). Structural to the mount, not session/state.
Regression marker: committing and pushing from Cowork on this folder used to work for me. So either
--delete-denywas added in a recent Cowork update, or some other change made the same flag start tripping where it previously didn't. Worth a look at when in the release history this flag was introduced or changed on the macOS build.Workaround: commit/push from a native shell (Claude Code CLI on the same path works fine — same files, different I/O route, no bindfs in the way). Work stays on disk; only GitHub backup trails the Cowork session until then.
Adding this thread to the broader Cowork sandbox failure-surface cluster framing I've been tracking, plus a CLI-side partial defense.
This is structurally the same shape as #38993 (virtiofs FUSE serves stale files) and #62932 (P1 critical, FUSE inode metadata frozen for the session) — the FUSE bridge between the Windows host and the Linux sandbox honours some POSIX operations (read, create-write) but rejects others (unlink, content invalidation). The unlink-denial you describe sits next to the stale-content / NUL-padded shrink already documented in those threads. Five sub-patterns now articulated in the bridge layer:
.git/configafter host-side merges (#62932)I shipped a CLI-side advisory hook today (2026-05-28) that partially covers the unlink-denial case:
cowork-fuse-staleness-watcher.sh(PR #410, 25 tests passing). The matcher already fires ongit rm/git clean/git mvinside/sessions/<id>/mnt/(and ongit add/commitwhose internal unlinks against.git/index.lockare the actual failure path you describe), so a CLI session that runsgit commitover the mounted Windows folder will see the warning before the partialgit addlands and leaves a stuck.git/index.lock.The hook does NOT fix the underlying FUSE rejection — that's server-side from the operator's perspective. What it does cover:
.git/cleanup, which matches your repro)Read/Edit/Write) which bypass FUSE entirely and are authoritative for the working treegit rev-parse/log/show) which don't touch the unlink path and remain functionalThe five-sub-pattern framing of Cluster 11 is in cc-safe-setup/docs/cluster-tracker.html (free reference). Whether an Anthropic-side acknowledgment of unlink-denial as a structural-not-racy bug would surface in the changelog or the Cowork docs would be useful for operators triaging this case — happy to refine the framing if more sub-patterns come in.
Confirmed: unlink from bash breaks all git operations on Windows mounts
We experienced this repeatedly on a Flutter project mounted on Windows via VirtioFS in Claude Cowork / Claude Desktop.
What happens: Git operations (commit, rebase, pull) leave
.git/index.lock,.git/HEAD.lock,refs/heads/master.lock, and.git/objects/maintenance.lockbehind. Any attempt torm/unlinkthese from the Linux bash sandbox fails silently or with permission denied — even for files the sandbox just created. They're visible inls, butrm -freturns exit 0 and the file remains.Cascading effect: once a single lock file accumulates, all subsequent git commands fail with "unable to lock" errors, making git unusable from the sandbox for the rest of the session.
Reliable workaround: write a
.batfile from the sandbox (writes work fine), then run it from Windows File Explorer — the native Windows process has the NTFS permissions the sandbox lacks, sodel /f /qon the locks actually succeeds.Alternative workaround: use git plumbing (
GIT_INDEX_FILE+read-tree+write-tree+commit-tree+ push by hash) which never creates the lock files in the first place.Environment: Anthropic Cowork / Claude Desktop, Windows 11, VirtioFS mount, Git 2.34.1 (Linux sandbox).
Follow-up from the same macOS setup (Darwin 24.2.0), two things since my 23 May comment.
Why locks specifically get orphaned — a rename-vs-unlink asymmetry. Git's lockfile protocol finishes a successful save by renaming the lock into place (
index.lock→index), which bindfs allows; it only callsunlink()on the lock when cleaning up an aborted/interrupted save, which--delete-denyblocks. So completed commits leave nothing, but any save killed mid-flight orphans a lock the sandbox can't remove —.git/index.lock/.git/HEAD.lockthen accumulate and block the next session with "Another git process seems to be running." Confirmed today: bothrmandmvon.git/index.lockreturnOperation not permittedinside.git/.In-Cowork recovery (didn't have this on 23 May). You don't have to drop to a native shell. When a git write hits the orphaned lock, Cowork prompts to grant delete access to the working folder; allowing it lets
rm .git/index.locksucceed from the bash sandbox and the save proceeds. Allowing it persistently means a future orphaned lock can be cleared in-session instead of blocking until you switch to the CLI.Common real-world trigger. The single-
rmrepro above is the root cause; in practice the orphaned lock most often arises from concurrency. Running two Cowork chats on the same folder, both on an auto-"commit and push after every change" workflow, reliably collides on the git index/ref lock ("Another git process seems to be running"). On a normal filesystem that race is transient — the loser retries, the winner's lock clears. Here, because the sandbox can'tunlinkinside.git/, any collision that leaves a lock behind can't be cleared from within Cowork, so a normally self-healing race becomes a hard block. Two auto-saving chats on one folder reproduces it quickly. (The contention is deterministic; whether a given collision orphans a lock depends on a save being interrupted while holding it — which the concurrency makes likely.)Thanks for laying out the root cause so clearly — that rename/unlink
asymmetry explains exactly what I'd been seeing on my end (VirtioFS on
Windows).
One thing that might add to the picture: today, working in Cowork on
Windows, I ran into something related but a bit different. A native Windows
script wrote a file and confirmed on the spot, using a native command, that
the content landed
correctly on disk. But reading that same file from the Linux sandbox (cat,
wc -l) kept returning the old version — same byte size, same mtime as
before the change — for over a minute afterward. So the write itself was
fine; what failed was the sandbox never invalidating its cached view of the
file. It only cleared up once I opened the file in a native Windows app
(Notepad), which showed the updated content right away.
Might be the flip side of what you described: if the rename does propagate
but the sandbox doesn't refresh its read cache for files changed from
outside it, that
would explain cases where it looks like "the write never landed" when it
actually did — it's the read afterward that's lying.
Thanks again for digging into this,
Rober
El sáb, 20 jun 2026 03:44, Ben Coombs @.***> escribió:
Also reproduces on macOS — not Windows-specific.
Same structural unlink() denial on a Cowork session on macOS (Apple silicon). The repo is a normal local folder, mounted into the sandbox over FUSE (fuseblk / type fuse). POSIX perms show the files owned by the sandbox uid, but unlink is rejected regardless; rename() and in-place overwrite are allowed.
Control test (mount vs. off-mount, brand-new file, no race window):
off the mount (sandbox ext4) — fine
$ cd /tmp && touch foo && rm foo && echo ok
ok
on the mounted repo — denied
$ cd /sessions/<session>/mnt/<repo> && touch foo && rm foo
rm: cannot remove 'foo': Operation not permitted
The knock-on for git is worse than "commit fails": because git clears .git/index.lock with unlink(), even a read-only git status leaves a fresh index.lock behind that the sandbox then cannot remove, so it blocks the next command:
$ git status
warning: unable to unlink '/sessions/<session>/mnt/<repo>/.git/index.lock': Operation not permitted
$ find .git -name '*.lock'
.git/index.lock # ← left behind by a plain status, blocks the next op
So the friction is continuous, not occasional — each git invocation poisons the following one.
Filesystem-only workaround that does work (since rename is permitted but unlink is not): keep the working tree on the mount but move git's metadata off-mount, so all lock files land on a filesystem where unlink works:
git --git-dir=/tmp/repo.git --work-tree=/sessions/<session>/mnt/<repo> <cmd>
cp -a <mount>/.git /tmp/repo.git first (copy is create+read, both allowed). Commits/pushes then run clean with zero orphaned locks on the mount; the commit is delivered to origin and the host repo syncs by pulling. This is a palliative, not a fix — the underlying request stands: the sandbox should permit unlink() on the mounted folder (or the "Cowork can manage version control" workflow should be documented as unavailable on a mounted host repo until it does).
Suggested label update: this is not platform:windows only — please broaden to include macOS.