[BUG] WSL2: a read-deny path symlinked into /mnt/c fails bwrap for every Bash command, and is unremovable when it comes from managed settings

Status Open
Reported on v2.1.268
Maintainer reply None cached
Activity 0 comments · opened Sep 12, 2026

Summary

On WSL2, every sandboxed Bash command fails during bwrap setup when a read-deny path is a symlink pointing into the Windows mount (/mnt/c).

This is the same failure as #45122, which was closed as stale on 2026-05-27 and then locked. It still reproduces on 2.1.268, so I am filing a fresh report rather than commenting on a locked thread.

The part that is new since #45122: when the deny rule arrives through enterprise managed settings, the user cannot remove it. Read and Edit deny rules are merged into the sandbox configuration, and a deny entry added by one scope cannot be removed by another. The Bash tool is then unusable on that machine and the affected user has no setting they can change to recover.

Environment

  • Claude Code 2.1.268, native sandbox (bwrap)
  • WSL2, Ubuntu 26.04 LTS, kernel 6.18.33.2-microsoft-standard-WSL2
  • ~/.aws is a symlink to /mnt/c/Users/<user>/.aws, a common way to share credentials with the Windows host

Repro

  1. In WSL2, make one of the deny paths a symlink into the Windows mount:
ln -s /mnt/c/Users/<user>/.aws ~/.aws
  1. Enable the sandbox and deny reads on that path, for example in settings.json:
{
  "sandbox": {
    "enabled": true,
    "filesystem": { "denyRead": ["~/.ssh", "~/.aws", "~/.config/gcloud"] }
  }
}
  1. Ask Claude to run any Bash command, for example ls.

Every command fails before the command itself runs:

bwrap: Can't mount tmpfs on /newroot/home/<user>/.aws: No such file or directory

The path exists. It is a symlink whose target lives on the Windows mount.

What is and is not affected

  • Deny paths that do not exist are skipped silently. Only the symlinked one fails. We confirmed this by creating ~/.ssh and ~/.config/gcloud as real empty directories: those passed, and the run still failed on the symlinked ~/.aws.
  • The in-session diagnosis suggests mkdir -p ~/.aws, which does not apply here. The directory is not missing; it is a symlink.
  • macOS (Seatbelt) is unaffected. It does not mask by mounting.

Why the user cannot work around it

Per the sandboxing documentation, Read and Edit deny rules are merged into the final sandbox configuration, and a deny entry can be added by any scope but removed by none.

On a machine where Read(~/.aws/**) comes from enterprise managed settings, we verified:

  • removing ~/.aws from the user's own sandbox.filesystem.denyRead and sandbox.credentials.files does not help, and the /sandbox Config tab still lists ~/.aws among the denied paths
  • the deny cannot be removed at the user scope at all
  • the remaining options are replacing the symlink with a real directory, or changing the managed settings, neither of which is available to the user

Expected

Setting up the deny mask should not fail the whole sandbox when one deny path cannot be masked. Mask without following the symlink, or skip the entry with a warning, the way non-existent paths are already skipped.

Impact

On any WSL2 machine where a denied path is symlinked into /mnt/c, the Bash tool stops working entirely the moment the sandbox is enabled. Sharing ~/.aws or ~/.ssh with the Windows host this way is a normal WSL setup, so this is reachable without any unusual configuration. When the deny arrives from managed settings, the user cannot restore Bash themselves.

Related

  • #45122, the same symptom with a symlinked ~/.aws, closed as stale and locked, still reproducing on 2.1.268
  • #86855, the same class of failure (a deny path that cannot be masked brings down the whole sandbox) with a different cause (a read-only /nix/store)

A smaller, separate observation from the same machine: on Linux the glob deny rule Read(./.env.*) is reported as ignored, with the warning Glob patterns not fully supported on Linux. The same settings therefore take effect differently on macOS and Linux. I am not filing that here.

View original on GitHub ↗