Claude Code Permission Spam? Just Ask It to Fix Itself
If you're using Claude Code you've probably toggled between "approve every single file read" and --dangerously-skip-permissions. There's a middle ground and it's embarrassingly simple: just ask Claude to configure its own permissions.
Paste this prompt into Claude Code and it'll inspect your current settings, discover your local tooling, and apply a sane policy:
Configure Claude Code global permissions for a non-destructive auto-approve workflow.
Policy:
1) Auto-allow:
- Read-only file/system inspection
- Search, diff, and codebase analysis
- Read-only git operations
- Non-destructive tooling checks and dry-runs
2) Ask first:
- File/content modifications and deletions
- State-changing git actions (staging, commit, push, history rewrite)
- Process control and privilege escalation
- Package install/upgrade/remove operations
- Any action that changes external systems/services
3) Hard-deny:
- Writes under ~/.ssh/**
- Writes under ~/.aws/**
- Writes under ~/.gnupg/**
MCP servers:
- Discover all installed MCP servers.
- Use prefix wildcards on tool names, not individual tool entries.
- Read-only operations (list_*, get_*, search_*) go in allow.
- Write operations (create_*, update_*, delete_*, save_*) go in ask.
- For fully read-only servers, allow the whole server: mcp__servername__*
Requirements:
- Preserve all existing non-permission settings.
- Discover local tooling and include safe read-only usage patterns.
- Unknown actions default to ask-first.
- Output the final merged settings plus a short summary of allow/ask/deny groups.
That's it. Claude reads your existing config, figures out what tools you have installed, and writes a permission set that auto-allows anything read-only while keeping a human in the loop for writes, pushes, and installs.
When granular patterns stop working
After a few weeks of the granular approach I started getting prompted on stuff that should've been auto-allowed. Turns out Claude chains commands. It runs cd /some/worktree && git diff --stat HEAD and the whole string has to match your pattern, not each subcommand. My Bash(git diff *) pattern doesn't match because the string starts with cd, not git diff.
I had 127 individual Bash patterns. That's insane.
Here's what I replaced all of them with:
{
"permissions": {
"allow": ["Bash"],
"ask": [
"Bash(*git commit*)",
"Bash(*git push*)"
]
}
}
"Bash" allows everything. The ask entries use wildcards on both sides so cd /some/path && git commit -m "fix" still triggers a prompt. ask is checked before allow, so the more specific patterns win.
If you only care about guarding two things, commits and pushes, this is the move. Stop enumerating every safe command. Enumerate the dangerous ones.
MCP tools: use prefix wildcards
If you have MCP servers (Linear, Datadog, etc.), don't list every tool individually. Claude Code supports wildcards on tool names:
{
"permissions": {
"allow": [
"mcp__linear-server__list_*",
"mcp__linear-server__get_*",
"mcp__linear-server__search_*",
"mcp__datadog-mcp__*"
],
"ask": [
"mcp__linear-server__create_*",
"mcp__linear-server__save_*",
"mcp__linear-server__update_*",
"mcp__linear-server__delete_*"
]
}
}
Allow all reads by prefix, prompt for writes by prefix. For read-only services like Datadog or Context7, just allow the whole server with mcp__datadog-mcp__*.
I went from ~170 permission lines to ~30. Should've done this from day one.
Fix garbled commit messages
If you're seeing overlapping mangled text in the commit approval dialog, it's the HEREDOC format. git commit -m "$(cat <<'EOF' ... EOF)" doesn't render properly in the terminal preview.
Add this to your CLAUDE.md:
Commit message format: Do NOT use HEREDOC $(cat <<'EOF' ... EOF) for
git commit messages. Write the message to /tmp/.commit-msg with the
Write tool, then: git commit -F /tmp/.commit-msg
Worth noting
- Keep hard-deny small. Only credential paths. If your deny list is growing you're overcomplicating it.
- Review the first run per repo. Let it discover repo-specific tooling, adjust once, then forget about it.
git logis notgit push. Classify by effect, not command name. The prompt handles this already.- Start granular, go nuclear when it annoys you. The bootstrap prompt gives you fine-grained patterns. When you get tired of approving compound commands, collapse to
"Bash"+ guarded operations. settings.local.jsonaccumulates cruft. Every time you click "Yes, and don't ask again" it appends an entry. After switching to wildcards, the local file is mostly redundant. Clean it out periodically.
Save the prompt as a skill if you bootstrap environments often. One prompt, runs in 30 seconds, and you stop clicking "Allow" on cat.