Permissions duplicated in settings.local.json despite existing grants in settings.json
Bug Description
When a user clicks "Always allow" on a permission prompt, Claude Code writes the permission to .claude/settings.local.json even when the exact same permission already exists in .claude/settings.json. This results in duplicate permission entries across the two files.
Steps to Reproduce
- Create a project with
.claude/settings.jsoncontaining explicit permission grants, e.g.:
``json``
{
"permissions": {
"allow": [
"Bash(grep:*)",
"Bash(ls:*)",
"Bash(wc:*)"
]
}
}
- Commit
settings.jsonto the repo (it's not gitignored). - Start a Claude Code session in that project.
- When Claude triggers a command that matches an already-granted permission (e.g.
grep), in some cases it still prompts for permission. - Click "Always allow".
- Observe that the permission is written to
.claude/settings.local.jsondespite already being present insettings.json.
Observed Behavior
.claude/settings.local.json accumulates duplicate permissions that are already granted in .claude/settings.json.
Real-world example from a monorepo with submodules:
Root .claude/settings.json already contains:
"Bash(grep:*)", "Bash(grep *:*)"
Root .claude/settings.local.json also contains:
"Bash(grep:*)"
Same pattern in submodules — e.g. test-business-rules/.claude/settings.json grants Bash(wc:*) and Bash(ls:*), yet the same entries appear in its settings.local.json.
Expected Behavior
- The "Always allow" write path should check whether the permission is already granted by
settings.jsonbefore writing tosettings.local.json. - If a permission is already covered by
settings.json, the prompt should not appear in the first place.
Environment
- Claude Code CLI (latest, installed via npm)
- Linux (Ubuntu 24.04, kernel 6.8)
- Project structure: monorepo with git submodules, each submodule has its own
.claude/settings.json
Additional Context
This might be related to how permission resolution works across the settings hierarchy (project settings.json vs personal settings.local.json). It seems like the read path (checking if a permission is granted) and the write path (saving "Always allow" choices) may not share the same resolution logic — the write path appears to always target settings.local.json without consulting settings.json first.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗