claude-code - 💡(How to fix) Fix [BUG] Cowork sandbox: unlink(2) returns EPERM on owned files, breaks .git lock cleanup

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

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.

Root Cause

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.

Fix Action

Fix / Workaround

  • 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 -delete before the next Cowork-side git operation. This is not a one-time setup cost; it recurs every cycle.
  • The git worktree add workaround helps but is partial. Creating a worktree at /tmp/<host-repo>-{branch}-worktree and working from it lets Cowork commit to a feature branch without needing git checkout at the Cowork mount (which would otherwise fail trying to unlink working-tree files during the checkout swap). The unlink-EPERM on .git/*.lock cleanup still applies via the worktree, but at least the checkout problem is sidestepped. The worktree workaround does not cover git stash, git rebase, git reset --hard, or git push (push has a separate cause — see below).
  • Outbound network is also blocked (separate issue, mentioned for completeness): git push from inside the Cowork sandbox fails with Connection closed by UNKNOWN port 65535, suggesting the sandbox has no outbound reachability to the host project's git remote. The host shell handles git push. We don't expect this in scope for the unlink-EPERM report, but flagging since it's the same operational surface.

Code Example

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

---

$ 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

---

# 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)
RAW_BUFFERClick to expand / collapse

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: lsattr and ls -l of 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 -delete before the next Cowork-side git operation. This is not a one-time setup cost; it recurs every cycle.
  • The git worktree add workaround helps but is partial. Creating a worktree at /tmp/<host-repo>-{branch}-worktree and working from it lets Cowork commit to a feature branch without needing git checkout at the Cowork mount (which would otherwise fail trying to unlink working-tree files during the checkout swap). The unlink-EPERM on .git/*.lock cleanup still applies via the worktree, but at least the checkout problem is sidestepped. The worktree workaround does not cover git stash, git rebase, git reset --hard, or git push (push has a separate cause — see below).
  • Outbound network is also blocked (separate issue, mentioned for completeness): git push from inside the Cowork sandbox fails with Connection closed by UNKNOWN port 65535, suggesting the sandbox has no outbound reachability to the host project's git remote. The host shell handles git 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

  1. 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?
  2. If intentional, what's the recommended workflow for git operations from inside the sandbox? Is there a sanctioned way to run git commit such that lock-file cleanup succeeds? (We haven't found one in our exploration.)
  3. 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.
  4. 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).

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix [BUG] Cowork sandbox: unlink(2) returns EPERM on owned files, breaks .git lock cleanup