sandbox.filesystem.denyWrite/denyRead silently ignore relative paths; only absolute paths work

Resolved 💬 5 comments Opened Apr 18, 2026 by MaxCan-Code Closed May 26, 2026

Summary

When configuring sandbox.filesystem.denyWrite and denyRead in settings.json, relative path patterns are silently ignored — no bind-mount is created, writes and reads still succeed, and no warning is emitted. Only absolute paths take effect.

Repro

Environment: Linux x86_64 (aarch64 wrapper), Claude Code 2.1.114 (Nix-built precompiled binary).

Works — absolute path creates a /dev/null devtmpfs bind-mount, same mechanism as the hardcoded shadows for .bashrc / .zshrc / .gitconfig / etc.:

{
  "sandbox": {
    "filesystem": {
      "denyWrite": ["/home/user/project/tmp", "/home/user/project/tmp/**"],
      "denyRead":  ["/home/user/project/tmp", "/home/user/project/tmp/**"]
    }
  }
}

After relaunch:

  • mount | grep project/tmpdevtmpfs on /home/user/project/tmp type devtmpfs (ro,nosuid,nodev)
  • ls -la tmpcrw-rw-rw- ... 1, 3 tmp (char device = /dev/null)
  • mkdir tmpFile exists
  • touch tmp/xNot a directory
  • cat tmp/xNot a directory

Does nothing — every relative variant I tried:

{
  "sandbox": {
    "filesystem": {
      "denyWrite": ["./tmp", "./tmp/**", "tmp", "tmp/**", "**/tmp", "**/tmp/**"],
      "denyRead":  ["./tmp", "./tmp/**", "tmp", "tmp/**", "**/tmp", "**/tmp/**"]
    }
  }
}

After relaunch:

  • No bind-mount created (mount | grep MaxCan-Code/tmp → empty)
  • mkdir tmp && touch tmp/foo → succeeds
  • cat tmp/foo → succeeds
  • No warning logged anywhere I could find

Expected behavior

At least one of:

  1. (Preferred) Accept relative paths, resolved against the project root — so the same rule works across projects, mirroring how the hardcoded shadow list (.bashrc, .zshrc, …) is project-relative.
  2. Emit a visible warning at startup when a denyWrite / denyRead entry can't be applied, so users aren't silently unprotected.
  3. Document that absolute paths are required.

Why it matters

Users who try relative paths (reasonable, since the built-in shadow list is project-relative) silently get no enforcement and assume the feature is working. And a user wanting a path like ./tmp/ shadowed in every project has to hardcode the home path per-project rather than writing it once in ~/.claude/settings.json.

View original on GitHub ↗

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