[BUG] Cowork sandbox: unlink(2) returns EPERM on owned files, breaks .git lock cleanup
Subject
Cowork sandbox: unlink(2) returns EPERM on files the sandbox user owns with rw------- mode, breaking .git/*.lock cleanup
Summary
Inside a Cowork sandbox session, unlink(2) fails with EPERM ("Operation not permitted") on regular files that the calling sandbox user owns with mode rw-------. Truncation (echo > file), chmod, and content writes all succeed; only the unlink-class operations (rm, unlink, mv over existing destination, find -delete) fail. Standard POSIX permission semantics permit unlink in this state; the cause appears to be environmental (mount options, LSM profile, container security layer, or similar layer above POSIX), not a permissions misconfiguration on the file or directory itself.
The most operationally painful consequence is that git cannot clean up its transient .git/*.lock files. Every commit succeeds at writing the object data but leaves orphaned lock files, blocking the next git operation in the same session until an external actor (host shell) deletes them.
Environment
- Cowork sandbox (Anthropic-managed; whatever the current production build is at the time of this report — session-id / image-id available on request if Anthropic's intake form requests it).
- The host project is a normal git repository (
git version 2.x, standard layout, no submodules, no LFS, no special filesystem in the repo path). - The repository is mounted into the sandbox at a stable path (Anthropic-determined; from inside the sandbox it appears as a regular directory).
Symptom
Verbatim git commit output from inside the Cowork sandbox (representative, across many sessions):
warning: unable to unlink '.git/objects/{NN}/tmp_obj_{XXX}': Operation not permitted
warning: unable to unlink '.git/HEAD.lock': Operation not permitted
warning: unable to unlink '.git/objects/maintenance.lock': Operation not permitted
[branch sha] commit message
The commit lands (object data is written, ref is updated, working tree is consistent). The warning-listed lock files persist on disk.
Post-commit lock-file inventory (typical):
$ find .git -name "*.lock" -o -name "tmp_*"
.git/HEAD.lock
.git/index.lock
.git/objects/maintenance.lock
.git/objects/{NN}/tmp_obj_{XXX}
.git/refs/heads/{branch}.lock # appears when refs are updated
.git/worktrees/{wt}/index.lock # appears when committing from a worktree
The next git operation in the same Cowork session that touches any of these resources fails (the .git/index.lock blocks any operation that modifies the index; .git/HEAD.lock blocks ref updates; etc.).
Minimal reproducer
Run inside a Cowork sandbox, in any git repository the sandbox can access:
# 1. Confirm file ownership and mode look writable + unlinkable.
touch /tmp/reproducer-file
ls -la /tmp/reproducer-file
id
# 2. Truncation and chmod succeed.
echo "content" > /tmp/reproducer-file && echo "write OK"
chmod 600 /tmp/reproducer-file && echo "chmod OK"
# 3. Unlink fails with EPERM.
rm /tmp/reproducer-file
# Expected: silent success.
# Actual: rm: cannot remove '/tmp/reproducer-file': Operation not permitted
# 4. Strace narrows it to the unlinkat syscall.
strace -e trace=unlinkat rm /tmp/reproducer-file 2>&1 | head -5
# Expected: unlinkat(...) = 0
# Actual: unlinkat(...) = -1 EPERM (Operation not permitted)
The strace step is optional; the first three steps reproduce the symptom and rule out POSIX-level misconfiguration.
What we've ruled out
- File ownership / mode: the calling sandbox user owns the file; mode is
rw-------. POSIX permits the owner to unlink files in a directory they have write+execute on. (We confirmed write+execute on the parent directory; truncation through the same path succeeds, which requires the same directory traversal.) - Git is healthy: same repository, same commits, identical operations from the host shell and from Claude Code's terminal succeed without warnings and leave no orphan locks. The failure mode is sandbox-environment-specific, not git-specific.
- A single operation type: not a one-off race. The behavior is consistent across every session and every commit.
- Not a sticky-bit / chattr-immutable issue at first glance:
lsattrandls -lof the affected files don't show anything unusual, but we haven't done a deep filesystem-attribute audit — Anthropic's internal tooling is better-positioned for that.
Operational impact
- Per-session recurring friction. Every Cowork-side commit cycle leaves orphan locks. The host-shell recovery is
find ~/path/to/repo/.git -name "*.lock" -print -deletebefore the next Cowork-side git operation. This is not a one-time setup cost; it recurs every cycle. - The
git worktree addworkaround helps but is partial. Creating a worktree at/tmp/<host-repo>-{branch}-worktreeand working from it lets Cowork commit to a feature branch without needinggit checkoutat the Cowork mount (which would otherwise fail trying to unlink working-tree files during the checkout swap). The unlink-EPERM on.git/*.lockcleanup still applies via the worktree, but at least the checkout problem is sidestepped. The worktree workaround does not covergit stash,git rebase,git reset --hard, orgit push(push has a separate cause — see below). - Outbound network is also blocked (separate issue, mentioned for completeness):
git pushfrom inside the Cowork sandbox fails withConnection closed by UNKNOWN port 65535, suggesting the sandbox has no outbound reachability to the host project's git remote. The host shell handlesgit push. We don't expect this in scope for the unlink-EPERM report, but flagging since it's the same operational surface.
What we'd like to understand
- Is the unlink-EPERM behavior intentional and expected as part of the sandbox's security posture (e.g., to prevent the sandbox from deleting files written by other Anthropic processes), or is it an unintended side-effect of the security layer?
- If intentional, what's the recommended workflow for git operations from inside the sandbox? Is there a sanctioned way to run
git commitsuch that lock-file cleanup succeeds? (We haven't found one in our exploration.) - If unintended, is this on the roadmap to address, or would a fix-request from us help prioritize it? We're happy to provide additional reproduction data, sandbox session IDs, or run targeted diagnostic commands if Anthropic engineers want to attach a probe.
- Is there a documented list of sandbox-environmental quirks Cowork users should expect? Documenting this one upstream would save other users the same diagnostic cycle.
Related upstream issues
This report describes the Cowork-managed Linux sandbox manifestation of what appears to be the same root-cause family as two existing reports:
- #55206 (Cowork on Windows; bash sandbox can create files on mounted host folder but unlink is denied)
- #52322 (sandbox.filesystem.allowWrite does not permit unlink / rm on macOS)
Filing this as a separate report since the originating environment is the Anthropic-managed Cowork Linux sandbox rather than user-host Windows or macOS bash sandboxes, but the underlying unlink-class denial pattern is identical. If Anthropic engineers conclude these all share a single fix, please close this report in favor of whichever upstream issue is the canonical reference.
Contact
GitHub issue author (filing via Claude Code on operator's behalf — Cowork's network isolation prevents direct filing from the sandbox).
7 Comments
Found 3 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
Technical Proposal: Cowork Sandbox unlink(2) EPERM Fix
Root Cause
Inside the Cowork sandbox,
unlink(2)returns EPERM on files owned by the sandbox user. The cause is an environmental restriction above POSIX — likely seccomp profile, mount options, or container security layer.Proposed Fix (4-layer approach)
1. Seccomp Profile Patch
unlinkatsyscall for files owned by sandbox UID2. Mount Option Adjustment
rwflag (notroor restrictive overlay)3. Git Workaround (Immediate Mitigation)
: > "$f"succeeds whererm "$f"fails4. Sandbox Filesystem Layer Fix
Implementation: ~7-10 hours
Quote: $1,500–$3,000
Solution: Handle EPERM on unlink in Cowork Sandbox
Root Cause
When the Cowork sandbox runs as a non-root user,
unlink(2)on files owned by a different user (e.g.,.gitlock files created by the host user) returnsEPERM. This breaks.gitlock cleanup and can leave the sandbox in a broken state.Fix
1. Add EPERM handling to the sandbox cleanup logic:
2. Run the sandbox process with a UID/GID that matches the host user:
3. Use
rm -fwith force flag for git lock cleanup:Why This Works
Zero Competition
No other solution comments submitted for this issue.
Adding two data points that may help localize this, since the same allowWrite-permits-write-but-not-unlink behavior shows up beyond Claude Code for VSCode.
It also affects Cowork (the Claude desktop app's local sandbox), not just the CLI/VSCode sandbox.
In a Cowork session the host folder is shared into a local Linux VM via virtiofs. Inside the sandbox, the owning user can create and overwrite files but cannot unlink or rename them. For example, "touch .git/x" succeeds, but "rm .git/x" fails with: rm: cannot remove '.git/x': Operation not permitted.
Characteristics match this issue exactly:
The sandbox user owns the parent directory with full rwx (drwx------), so it isn't a POSIX mode problem; unlink is denied above the POSIX layer.
Only unlink-class operations fail (rm, mv over an existing target, rename); content writes, truncation, and chmod all succeed.
It is global across mounts, not folder-specific. It fails in the connected repo folder and also in Cowork's own scratch/output mount (a different path), so it is not the folder's ACLs/flags or its location.
It persists identically across brand-new sessions (each gets a fresh mount).
Practical impact is the same as reported: git cannot complete index updates (it writes index.lock and then renames it over index), so commits cannot be made from inside the sandbox without leaving orphaned .git/*.lock files. git push is separately blocked by network isolation.
On macOS it appears to be OS-version-dependent, and this is the part that might pinpoint it.
Same Claude app version, same repo layout under ~/Documents, two machines:
macOS Sequoia 15.7.7 (Darwin 24): unlink is denied (EPERM), as above.
macOS Tahoe 26.2 (Darwin 26): unlink and rename work normally; in-sandbox git commits succeed with no orphaned locks.
Everything else was ruled out as the differentiator: identical app version, Full Disk Access (the working machine has it off, and enabling it on the failing machine changed nothing), iCloud Drive (disabling it changed nothing), folder ACLs/flags (the failure also occurs in the app's own non-~/Documents scratch mount), and MDM/endpoint-security software (none installed).
If the macOS confinement is a Seatbelt profile granting file-write-data but not file-write-unlink, this suggests Apple changed file-write-unlink enforcement (or its interaction with the profile) between Darwin 24 and Darwin 26, which would explain why the identical build behaves differently across the two OS versions. Happy to run targeted diagnostics or provide session IDs if that would help.
I am having this issue with both dispatch and cowork desktop(on Windows) where it fails or is denied the ability to delete. I get no prompts at all.
i just tried this:
Me: craete a test file and delete it
Claude:
Ran a command
Ran a command
Confirmed — I can create files in your folder but deletion is blocked by the sandbox, regardless of who created the file. You'll need to delete lu381uvr5.tmp manually from File Explorer.
I never get a prompt or anything, I have had to manually deleete things or dig through directories to find ranaom temp/ lock files.
It has the folders in both places, the trusted folders and the cowork files. Is there anyway to fix this issue or is it completely out of the users hands?
This reproduces on Mac as well.
<details>
<summary>Environment</summary>
through a connected folder, running Python, committing
</details>
Still reproducing as of 2026-08-04, and I have a consequence to add that I don't
think is in the original report:
git gc/git maintenancenever completes, sothe repo is never packed and the garbage accumulates permanently.
Environment
Consistent with @UFMurphy's Darwin 24 vs Darwin 26 finding above — this machine is
Darwin 24 (24G720), i.e. the failing side of that boundary.
What I found
A Cowork session made 4 commits to this repo across two working windows on
2026-08-04. All 4 commits succeeded — objects written, refs updated, nothing lost.
Afterwards the repo contained:
| Artifact | Count | Size |
|---|---|---|
|
.git/objects/{NN}/tmp_obj_*| 126 | 11.63 MiB ||
.git/objects/maintenance.lock| 1 | 0 bytes ||
.git/HEAD.lock/.git/index.lockorphans | 15 | 0 bytes |git count-objects -vHbefore cleanup:The garbage was 55% of
.git.git fsckreported no corruption — the repo washealthy, just heavily littered.
The part I think is new: maintenance is permanently blocked
Note
packs: 0with 133 loose objects. The orphaned.git/objects/maintenance.lockwas timestamped to the first Cowork session, which means every subsequent
git maintenancerun was blocked from that moment on. So the failure is not only"this session leaves garbage" — it is:
maintenance.lock.tmp_obj_*garbage accumulates withoutbound across sessions.
A repo used this way grows monotonically and never self-heals. On the host,
rm -f .git/objects/maintenance.lock && git gc --prune=nowtook it from 133 looseobjects / 0 packs to 0 loose / 1 pack, and
.gitfrom ~21 MiB to 9.7 MiB.Diagnostic tip: the sandbox stamps commits in UTC
Useful for anyone trying to work out which commits came from Cowork vs. the host:
the sandbox commits with a UTC clock. My host is
+0200, and the split is clean:Converted to local time, each
+0000commit matches a lock/garbage file mtime tothe minute. That correlation is how I attributed the residue.
Secondary observation (possibly the same root cause, possibly separate)
The Cowork session reported to me that it could not reorganize the repo *because it
was sandboxed* — while it had in fact been committing to that repo successfully all
along. Whatever operation it actually attempted failed with a permission error and
was surfaced as a blanket "I'm sandboxed" rather than as the specific unlink denial.
If the underlying error was the same
EPERMon unlink, then the reporting ismasking a fixable, specific failure behind a generic capability statement. I can't
confirm that from outside the sandbox — flagging it in case it helps localize.
Impact as experienced
The residue is invisible until it bites. Nothing warns you. In my case a later
git commitfrom the host failed outright on a stale.git/index.lockwith no gitprocess running, which is when I went looking. Somebody had already been coping with
it by hand — the repo had lock files moved aside with PID suffixes
(
index.lock.10568,stale_index.lock3, …), so it had recurred at least six timesbefore anyone diagnosed it.
Re-raising the reporter's original question: is git inside Cowork supported, and if
so, what is the sanctioned workflow?