A Claude Code permission problem is one of four things. The operating system refused, which shows as EACCES, EPERM, or Permission denied. The Claude Code permission system refused, which comes with an explanation and traces to a deny rule, an ask rule, a permission mode, or a hook. macOS refused, which shows as Operation not permitted in Desktop, Documents, or Downloads. Or the Bash sandbox refused, which happens only when you have turned it on. The message differs in each case, and so does the fix.
- Four mechanisms: OS, Claude policy, macOS folder protection, and the sandbox.
EACCESorEPERMmeans the filesystem, not Claude.Operation not permittedin Desktop or Documents means macOS.- Rules evaluate deny, then ask, then allow. Specificity never changes that order.
- A path rule on
WriteorGlobis accepted and never consulted. UseEditandRead. - Never
sudoto fix this. It makes the next one worse.
Telling them apart
| You see | Mechanism | Fix |
|---|---|---|
EACCES, EPERM, Permission denied | Operating system | Fix file ownership |
| A refusal with a reason, in Claude's own words | Claude Code policy | A deny rule, an ask rule, a mode, or a hook |
Operation not permitted in ~/Desktop or ~/Documents | macOS folder protection | Grant access, or move the project |
| A command fails only when the sandbox is on | The Bash sandbox | Widen the filesystem or network policy |
EACCES during npm install -g | npm prefix owned by root | User-writable prefix; never sudo |
| A tool Claude never even mentions | A bare-name deny rule | It was removed from Claude's context |
| Read works, write fails, no message | Folder protection, or a read-only mount | Check both |
Filesystem permissions
# who owns it?
ls -la path/to/file
ls -ld .
# root-owned files in your project, usually from a sudo you regret
find . -user root 2>/dev/null | head
# take them back
sudo chown -R "$(whoami)" .
# the npm classic: a global prefix owned by root
ls -ld "$(npm root -g)"
sudo chown -R "$(whoami)" ~/.npm
# and the install directory the native installer needs
test -w ~/.local/bin && echo writable || echo "not writable"
sudo mkdir -p ~/.local/bin && sudo chown -R "$(whoami)" ~/.local
macOS protected folders
macOS protects ~/Desktop, ~/Documents, and ~/Downloads. A process working there needs an explicit grant, and without one the failures are intermittent, poorly explained, and look exactly like a broken tool. The tell is Operation not permitted on a file you can open yourself in Finder.
mkdir -p ~/code
mv ~/Documents/my-project ~/code/
cd ~/code/my-project
claude
When Claude Code itself refuses
Now the useful part. A policy refusal has exactly four possible sources, and /permissions shows you the first two directly.
| Source | How to confirm | Note |
|---|---|---|
A deny rule matched | /permissions, or jq .permissions on your settings | Deny always beats allow |
An ask rule matched | Same | Prompts even when a narrower allow matches |
| The permission mode | /status, or Shift+Tab to see the current mode | Plan mode is read-only by design |
A PreToolUse hook exited 2 | /hooks, then run the hook by hand | Blocks before rules are even evaluated |
| Managed policy | /status shows whether managed settings are in effect | Cannot be overridden locally, by design |
| Outside the working directory | Compare the path with pwd | --add-dir, /add-dir, or /cd |
# what is configured, per scope
jq .permissions ~/.claude/settings.json
jq .permissions .claude/settings.json
jq .permissions .claude/settings.local.json
# is a hook doing it?
jq .hooks .claude/settings.json
# run the hook by hand with a realistic payload
echo '{"tool_name":"Bash","tool_input":{"command":"git push --force"}}' \
| .claude/hooks/guard.sh; echo "exit=$?"
One behaviour surprises people: a deny rule written as a bare tool name, such as Bash, removes the tool from Claude's context entirely, so Claude never knows it existed and never mentions it. A scoped rule such as Bash(rm *) leaves the tool available and blocks only matching calls. If Claude seems oddly unable to do something obvious, check for a bare-name deny before assuming a bug.
Permission modes, and what each one changes.
| Mode | Behaviour |
|---|---|
default | Prompts on first use of each tool. Labelled Manual in the CLI. |
plan | Reads and read-only commands only. No source edits. |
acceptEdits | Auto-accepts edits and common filesystem commands inside your directories. |
auto | Auto-approves with background safety checks against your request. |
dontAsk | Auto-denies anything not pre-approved. A refusal here is the mode, not a rule. |
bypassPermissions | Skips prompts. Isolated environments only. |
The rule that is accepted and never consulted
This one deserves its own section because it fails silently and looks like a Claude Code bug. File permissions are checked against Edit(path) and Read(path) rules only.
{
"permissions": {
"deny": ["Write(docs/**)", "Glob(secrets/**)"]
}
}
{
"permissions": {
"deny": ["Edit(docs/**)", "Read(secrets/**)"]
}
}
Two more details that decide real cases. Deny and ask rules match a directory name at any depth below the current directory, so Read(secrets/**) also covers nested copies. And when Claude touches a symlink, both the link and its target are checked: a symlink pointing at a denied file is itself denied, while an allow rule falls back to prompting rather than silently permitting.
The sandbox changes the shape of this
Claude Code ships an OS-level sandbox for the Bash tool. Turned on, it stops asking about each command and instead enforces a filesystem and network boundary that applies to every command and its child processes. That is a better trade than approving commands one at a time, and it produces a different failure message: a command that works in your shell fails inside the session because it wrote outside the working directory or reached a domain you have not allowed.
Sandbox requirements as of August 2026.
| Platform | Requirement |
|---|---|
| macOS | Nothing to install. Uses the built-in Seatbelt framework. |
| Linux and WSL2 | bubblewrap and socat, plus an optional seccomp filter |
| Native Windows | Not supported. Run Claude Code inside WSL2. |
sudo apt-get install bubblewrap socat # Ubuntu and Debian
# sudo dnf install bubblewrap socat # Fedora
# Ubuntu 24.04 and later restrict unprivileged user namespaces
sysctl kernel.apparmor_restrict_unprivileged_userns # 1 means you need an AppArmor profile for bwrap
# then, inside Claude Code
# /sandbox opens Mode, Overrides, Network, and a Dependencies tab if anything is missing
Questions people ask
Either the operating system refused, which shows as EACCES or EPERM with no explanation, or the Claude Code permission system refused, which always comes with a reason. Raw errno means fix file ownership. A reasoned refusal means a deny rule, an ask rule, the permission mode, or a PreToolUse hook.
Check ownership with ls -la. Root-owned files in your project or npm tree, usually from an earlier sudo npm install -g, are the common cause. Take ownership back with chown, use a user-writable npm prefix, and make sure ~/.local is writable for the native installer.
macOS protects Desktop, Documents, and Downloads, and the grant belongs to the process that is running. The tell is Operation not permitted on a file you can open yourself. Approve the prompt, grant your terminal access in System Settings, or move the project to somewhere ordinary such as ~/code.
Run /permissions to see the resolved rules and which settings file each came from. Rules evaluate deny, then ask, then allow, so a broad deny beats a narrower allow and you need to narrow the deny rather than add an exception. Also check /hooks: a PreToolUse hook that exits 2 blocks the call before rules are evaluated.
Most likely you wrote it against a tool that file permission checks do not consult. Only Edit(path) and Read(path) rules are checked for file paths; Write, NotebookEdit, MultiEdit, and Glob path rules are accepted, warned about at startup, and never enforced. Use Edit(docs/**) instead of Write(docs/**).
No. It creates root-owned files that break later operations for your user, and the resulting errors give no hint about the cause. If a directory is not writable, change its ownership once with chown rather than running the tool as root.
It is scoped to the directory you launched it from. Use --add-dir at startup or /add-dir in a session to grant access, or /cd to relocate the session, which also loads that directory CLAUDE.md.
Not natively. The Bash sandbox runs on macOS, Linux, and WSL2, and on Windows you run Claude Code inside a WSL2 distribution. On Linux and WSL2 it needs bubblewrap and socat installed; the /sandbox panel lists anything missing.
Sources
Every figure above was read from these pages on August 2026. Vendors reprice without notice; if you find a stale number, tell us.