Permissions reloaded — building a tight allow-list
Beyond the safe defaults from Recipe 1.4: a project-scoped allow-list that pre-clears 90% of routine prompts without giving up the dangerous-command guardrails.
Permissions reloaded
Recipe 1.4 set up the safe defaults — Bash blocked, file edits scoped, network calls reviewed. That keeps you out of trouble. But after a few weeks of real use you start clicking "allow" on the same five commands every session: npm test, git status, git diff, ls, cat package.json. Each click costs three seconds and your concentration.
This recipe builds a tight allow-list that pre-clears those routines without re-opening the gates the safe defaults closed.
Step 1: Spot the prompts you keep approving
Open ~/.claude/transcripts/ (or use the /fewer-permission-prompts skill if you have it installed) and skim the last week. You're looking for tool calls you approved more than three times. Those are candidates for an allow-rule.
The skill is the lazy path:
/fewer-permission-prompts
It scans your transcripts, ranks the most-clicked commands, and produces a JSON snippet ready to paste. If you don't have it, do it manually — grep "Allow this" ~/.claude/transcripts/*.jsonl | head -50 is enough.
Step 2: Add a project-scoped allow-list
Project-scoped lives in <repo>/.claude/settings.local.json. Things in here only apply when Claude is launched from that directory:
{
"permissions": {
"allow": [
"Bash(npm test)",
"Bash(npm run build)",
"Bash(git status)",
"Bash(git diff)",
"Bash(git log:*)",
"Read(./*)",
"Edit(./src/**)",
"Glob(./*)",
"Grep(./*)"
]
}
}
Two things to note:
Bash(git log:*)— the:*suffix lets any flag through (--oneline,-10,--graph). Without it only the baregit logis allowed.Edit(./src/**)— scopes edits to your source tree../node_modules/**won't auto-approve.
Step 3: Deny rules for hard limits
Allow-list is offense. Deny-list is defense. Even when an allow-rule technically matches, a deny-rule can override:
{
"permissions": {
"allow": [
"Bash(*)"
],
"deny": [
"Bash(rm -rf*)",
"Bash(git push --force*)",
"Bash(git reset --hard*)",
"Bash(prisma migrate reset*)",
"Bash(docker system prune --volumes*)"
]
}
}
The point is not to stop a malicious agent (the model is on your side). The point is to add friction in front of the few commands that destroy hours of work in one keystroke.
Step 4: Verify settings parse
The validator on this step parses ~/.claude/settings.json (the global file). For project-scoped checks Claude reads .claude/settings.local.json automatically the next time you launch it from that directory.
Run aiguide_validate_step. If it fails, fix the JSON — a stray comma is the usual cause.
python3 -m json.tool ~/.claude/settings.json > /dev/null && echo "valid" || echo "invalid or missing"
Step 5: Reload and watch
In an open Claude session, type /permissions to see the merged allow-list (global + project + session-level). The project rules should appear when you launch from that repo.
Test by running npm test once. No "Allow this?" prompt → you saved a click forever.
Re-audit every two months. Three signals that you need to revisit:
- A new command shows up in the top-five "Allow this" list. Add it.
- A command in the allow-list hasn't been used in a month. Remove it.
- You added a deny rule because something scared you. Keep that. Promote it to global if it would scare you in any project.