[BUG] Claude doesn't respect project-level permissions when writing `settings.json` or `.mcp.json`
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
I've occasionally hit issues where Claude should ostensibly have permission to edit settings.json or .mcp.json, but it still asks to edit the file.
Most recent example: I'm at the root of a repo, trying to edit $REPO_ROOT/todoist-manager/.mcp.json (nested inside a subdirectory). Claude is asking me:
⏺ Update(todoist-manager/.mcp.json)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Edit file
todoist-manager/.mcp.json
╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
.....edit contents....
╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
Do you want to make this edit to .mcp.json?
❯ 1. Yes
2. Yes, allow all edits during this session (shift+Tab)
3. No
The $REPO_ROOT/.claude/setting.json at the repo root has the following:
{
"permissions": {
"allow": [
"Read(${PWD}/**)",
"Edit(${PWD}/**)",
"Write(${PWD}/**)",
"Glob(${PWD}/**)",
"Grep(${PWD}/**)",
"Read(~/.gitconfig)"
],
"deny": [
"Read(${PWD}/.env)",
"Read(${PWD}/.env.*)",
"Read(${PWD}/secrets/**)",
"Bash(rm -rf:*)",
"Bash(sudo:*)"
],
"ask": [
"Bash(git push:*)"
]
}
}
My understanding is that this should be allowed per Edit(${PWD}/**). I've also tried Edit(./**) and Edit(~/..path..to...REPO_ROOT/**), to no avail.
This is a repeated problem, where the declared allowed permissions don't seem to be respected when editing Claude's own configuration files.
My global .claude/settings.json has the following:
{
"permissions": {
"allow": [
"Read(./**)",
"Edit(./**)",
"Write(./**)",
"Glob(./**)",
"Grep(./**)",
"Bash(cd *)",
"Bash(grep *)",
"Bash(ls *)",
"Bash(stat *)",
"Bash(pwd *)",
"Bash(mkdir *)",
"Bash(gh issue view *)",
"Bash(gh issue list *)",
"Bash(gh issue status *)",
"Bash(gh pr view *)",
"Bash(gh pr list *)",
"Bash(gh pr status *)",
"Bash(gh pr checks *)",
"Bash(gh pr diff *)",
"Bash(gh repo view *)",
"Bash(gh repo list *)",
"Bash(gh release view *)",
"Bash(gh release list *)",
"Bash(gh run view *)",
"Bash(gh run list *)",
"Bash(gh workflow view *)",
"Bash(gh workflow list *)",
"Bash(gh search *)",
"Bash(gh status *)",
"Bash(gh gist view *)",
"Bash(gh gist list *)",
"Bash(gh list *)",
"Bash(echo *)",
"Bash(git status *)",
"Bash(git diff *)",
"Bash(git log *)",
"Bash(git show *)",
"Bash(git add)",
"Bash(git add *)",
"Bash(git checkout)",
"Bash(git checkout *)",
"Bash(git commit -m *)",
"Bash(git push)",
"Bash(git push *)",
"Read(${HOME}/.claude/*)",
"Read(${HOME}/.vim/*)",
"Read(${HOME}/.nvim/*)",
"Read(${HOME}/.bash_aliases)",
"Read(${HOME}/.bashrc)",
"Read(${HOME}/.zshrc)",
"Read(${HOME}/.zprofile)",
"Read(${HOME}/.gitconfig)",
"Read(${HOME}/.vimrc)",
"Read(${HOME}/.tmux.conf)",
"Read(~/code/*.md)",
"Read(~/code/*.txt)",
"WebSearch",
"WebFetch(domain:anthropic.com)",
"WebFetch(domain:github.com)",
"WebFetch(domain:api.github.com)",
"WebFetch(domain:raw.githubusercontent.com)",
"WebFetch(domain:notion.com)",
"WebFetch(domain:todoist.com)",
"WebFetch(domain:npmjs.org)",
"WebFetch(domain:npmjs.com)",
"WebFetch(domain:pypi.org)",
"WebFetch(domain:crates.io)",
"WebFetch(domain:rubygems.org)",
"WebFetch(domain:packagist.org)",
"WebFetch(domain:nuget.org)",
"WebFetch(domain:maven.org)",
"WebFetch(domain:mvnrepository.com)",
"WebFetch(domain:developer.mozilla.org)",
"WebFetch(domain:docs.python.org)",
"WebFetch(domain:doc.rust-lang.org)",
"WebFetch(domain:golang.org)",
"WebFetch(domain:go.dev)",
"WebFetch(domain:php.net)",
"WebFetch(domain:ruby-doc.org)",
"WebFetch(domain:docs.oracle.com)",
"WebFetch(domain:reactjs.org)",
"WebFetch(domain:react.dev)",
"WebFetch(domain:vuejs.org)",
"WebFetch(domain:angular.io)",
"WebFetch(domain:svelte.dev)",
"WebFetch(domain:nextjs.org)",
"WebFetch(domain:nodejs.org)",
"WebFetch(domain:docs.npmjs.com)",
"Skill(google-doc-image-transcription)",
"Skill(prompt-engineer)",
"Skill(claude-settings-editing)"
],
"deny": [
"Read(*.env)",
"Bash(rm -rf /*)",
"Bash(rm -rf /)",
"Bash(sudo rm -rf /*)",
"Bash(sudo rm -rf /)",
"Bash(dd if=/dev/zero of=/dev/*)",
"Bash(dd if=/dev/random of=/dev/*)",
"Bash(mkfs*)",
"Bash(fdisk*)",
"Bash(parted*)",
"Bash(*fork*bomb*)",
"Bash(> /dev/sda*)",
"Bash(> /dev/nvme*)",
"Bash(> /dev/sd*)",
"Bash(cat /dev/urandom > *)",
"Bash(* | nc -l*)",
"Bash(* | netcat -l*)",
"Bash(* | socat *)",
"Bash(* | base64 -d | sh*)",
"Bash(* | base64 -d | bash*)",
"Bash(curl * | sh*)",
"Bash(curl * | bash*)",
"Bash(wget * | sh*)",
"Bash(wget * | bash*)",
"Bash(sudo passwd*)",
"Bash(passwd*)",
"Bash(sudo useradd*)",
"Bash(sudo userdel*)",
"Bash(sudo usermod*)",
"Bash(sudo groupadd*)",
"Bash(sudo groupdel*)",
"Bash(sudo adduser*)",
"Bash(sudo deluser*)",
"Bash(sudo chmod 777 /*)",
"Bash(sudo chown * /*)",
"Bash(sudo rm /etc/*)",
"Bash(sudo rm -rf /etc/*)",
"Bash(sudo rm /bin/*)",
"Bash(sudo rm /usr/*)",
"Bash(sudo > /etc/*)",
"Bash(nc -l*)",
"Bash(netcat -l*)",
"Bash(socat*)",
"Bash(nmap*)",
"Bash(masscan*)",
"Bash(*xmrig*)",
"Bash(*monero*)",
"Bash(*bitcoin*)",
"Bash(*miner*)",
"Bash(git add -f *)",
"Bash(git add --force *)",
"Bash(git push --force origin master*)",
"Bash(git push --force origin main*)",
"Bash(git push -f origin master*)",
"Bash(git push -f origin main*)",
"Bash(git reset --hard origin/master*)",
"Bash(git reset --hard origin/main*)",
"Bash(cat ~/.aws/*)",
"Bash(cat ~/.ssh/id_*)",
"Bash(cat /root/.ssh/*)",
"Bash(env | base64*)",
"Bash(printenv | base64*)",
"Bash(set | base64*)",
"Bash(bash -i >& /dev/tcp/*)",
"Bash(sh -i >& /dev/tcp/*)",
"Bash(python -c 'import socket*)",
"Bash(php -r '$sock*)",
"Bash(ruby -rsocket*)",
"Bash(perl -e 'use Socket*)",
"Bash(docker run --privileged *)",
"Bash(docker run --pid=host *)",
"Bash(docker run --net=host *)",
"Bash(docker run -v /:/host *)",
"Bash(docker run -v /etc:/etc *)",
"Bash(docker run -v /var/run/docker.sock:/var/run/docker.sock *)",
"Bash(history | grep -i password*)",
"Bash(history | grep -i token*)",
"Bash(history | grep -i secret*)",
"Bash(history | grep -i key*)",
"Bash(grep -r password /etc/*)",
"Bash(grep -r token /etc/*)",
"Bash(find / -name id_rsa*)",
"Bash(find / -name *.key*)",
"Bash(find / -name *.pem*)",
"Bash(shutdown*)",
"Bash(reboot*)",
"Bash(halt*)",
"Bash(poweroff*)",
"Bash(init 0*)",
"Bash(init 6*)",
"Bash(sudo shutdown*)",
"Bash(sudo reboot*)",
"Bash(sudo ufw disable*)",
"Bash(sudo iptables -F*)",
"Bash(sudo iptables --flush*)",
"Bash(sudo systemctl stop firewalld*)",
"Bash(sudo service iptables stop*)",
"Bash(curl * | sudo *)",
"Bash(wget * | sudo *)",
"Bash(curl -s * | sh*)",
"Bash(wget -qO- * | sh*)",
"Bash(rm /var/log/*)",
"Bash(rm -rf /var/log/*)",
"Bash(> /var/log/*)",
"Bash(echo > /var/log/*)",
"Bash(truncate -s 0 /var/log/*)",
"Bash(npm install -g * --unsafe-perm*)",
"Bash(pip install * --break-system-packages*)",
"Bash(gem install * --no-user-install*)",
"Bash(insmod *)",
"Bash(rmmod *)",
"Bash(modprobe *)",
"Bash(sysctl -w *)",
"Bash(chmod -R 777 /*)",
"Bash(chmod 777 /etc/*)",
"Bash(chmod 777 /bin/*)",
"Bash(chmod 777 /usr/*)",
"Bash(chown -R * /*)",
"Bash(*bitcoin-cli*)",
"Bash(*ethereum*)",
"Bash(*wallet.dat*)",
"Bash(*privatekey*)",
"Bash(find / -type f -delete*)",
"Bash(find / -type d -delete*)",
"Bash(rm -rf /home/*)",
"Bash(rm -rf /var/*)",
"Bash(rm -rf /opt/*)",
"Bash(gdb -p *)",
"Bash(ptrace *)",
"Bash(LD_PRELOAD=*)",
"Bash(sudo -l*)",
"Bash(sudo -V*)",
"Bash(sudo su*)",
"Bash(sudo su -*)",
"Bash(sudo -i*)",
"Bash(pkexec *)",
"Bash(export PATH=*)",
"Bash(export LD_LIBRARY_PATH=*)",
"Bash(export PYTHONPATH=*)",
"Bash(nsenter *)",
"Bash(docker run --cap-add=ALL *)",
"Bash(docker run --security-opt *)",
"Bash(systemctl mask *)",
"Bash(systemctl disable *)",
"Bash(systemctl daemon-reload*)",
"Bash(exec < /dev/tcp/*)",
"Bash(exec > /dev/tcp/*)",
"Bash(exec 3<>/dev/tcp/*)"
],
"//": "Started from https://github.com/dwillitzer/claude-settings/blob/main/settings.jsonc"
},
"hooks": {
"PreCompact": [
{
"matcher": "auto",
"hooks": [
{
"type": "prompt",
"prompt": "Context is about to be auto-compacted. Return {\"ok\": false} to stop and let the user choose /clear or /compact manually."
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "printf '\\a' > /dev/tty"
}
]
}
],
"PermissionRequest": [
{
"hooks": [
{
"type": "command",
"command": "printf '\\a' > /dev/tty"
}
]
}
]
},
"enabledPlugins": {
"lua-lsp@claude-plugins-official": true,
"gopls-lsp@claude-plugins-official": true
}
}
What Should Happen?
Claude should see the allowed permission and continue without asking.
Error Messages/Logs
Steps to Reproduce
ONE
Create a new repo with this structure:
.claude/
settings.json
subdir/
foo.txt
.mcp.json
TWO
Set the settings.json to the following:
{
"permissions": {
"allow": [
"Read(${PWD}/**)",
"Edit(${PWD}/**)",
"Write(${PWD}/**)",
"Glob(${PWD}/**)",
"Grep(${PWD}/**)",
"Read(~/.gitconfig)"
],
"deny": [
"Read(${PWD}/.env)",
"Read(${PWD}/.env.*)",
"Read(${PWD}/secrets/**)",
"Bash(rm -rf:*)",
"Bash(sudo:*)"
],
"ask": [
"Bash(git push:*)"
]
}
}
THREE
Start claude at the repo root and ask Claude to edit foo.txt. This will work.
FOUR
Ask Claude to edit subdir/.mcp.json. This will ask for permission, even though it's allowed.
FIVE
Ask Claude to edit .claude/settings.json. This will ask for permission, even though it's allowed in the settings.json.
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.20 (Claude Code)
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
iTerm2
Additional Information
When editing the settings.json, a slightly different prompt pops up: "Yes, and allow Claude to edit its own settings during this session". I suspect that Claude has some logic somewhere about requiring prompting to edit its own logic. It'd be good to be able to explicitly allow this.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗