headroom.walls.sh · permissions
Claude Code permissions
Stop Claude Code from asking permission for every command — configure allow and deny rules once in settings.json.
Why permissions exist
Claude Code can run shell commands, read and write files, and call external APIs. By default it asks for confirmation before any action that could have side effects — a bash command, a file write, a network request. This is safe but noisy once you've decided to trust Claude Code in a given context.
The permissions block in settings.json lets you pre-approve (or pre-block) specific patterns so Claude Code doesn't interrupt you for things you'd always allow.
The permissions block
{
"permissions": {
"allow": [
"Bash(git *)",
"Bash(npm run *)",
"Read(**)",
"Edit(**)"
],
"deny": [
"Bash(rm -rf *)",
"Bash(sudo *)"
]
}
}
Each entry is ToolName(pattern). Deny takes precedence over allow — if an action matches both, it's blocked.
Tool names
| Tool | What it covers |
|---|---|
| Bash | Any shell command. Pattern matches the full command string. |
| Read | Reading a file. Pattern matches the file path. |
| Edit | Editing an existing file. Pattern matches the file path. |
| Write | Writing a new file. Pattern matches the file path. |
| WebFetch | Fetching a URL. Pattern matches the URL. |
| WebSearch | Web searches. Pattern matches the query. |
| mcp__*__* | MCP server tools. Pattern: mcp__servername__toolname. |
Pattern syntax
| Pattern | Meaning |
|---|---|
| * | Matches anything within a single path segment or word |
| ** | Matches anything including path separators — use for file paths |
| git * | Any git subcommand and flags |
| npm run * | Any npm run script |
Common allow patterns
Git (all operations)
"Bash(git *)"
npm / yarn / pnpm
"Bash(npm *)", "Bash(yarn *)", "Bash(pnpm *)"
Build tools
"Bash(make *)", "Bash(cargo *)", "Bash(go *)", "Bash(swift *)"
Testing
"Bash(pytest *)", "Bash(jest *)", "Bash(rspec *)", "Bash(npm test)"
File operations (all files)
"Read(**)", "Edit(**)", "Write(**)"
File operations (project only)
"Read(src/**)", "Edit(src/**)"
Web fetching
"WebFetch(*)"
Common deny patterns
"Bash(rm -rf *)", "Bash(sudo *)", "Bash(curl * | bash)", "Bash(chmod 777 *)"
Where to put permissions
Two scopes — pick based on trust level:
| File | Use when |
|---|---|
| ~/.claude/settings.json | You trust Claude Code with git/npm/read globally — same rules everywhere |
| .claude/settings.json | Project-specific tools (Docker, kubectl, deploy scripts) — commit with the repo |
Project-level rules apply on top of user-level. Both allow lists merge; deny takes precedence across both.
A practical starting config
{
"permissions": {
"allow": [
"Bash(git *)",
"Bash(npm *)",
"Bash(make *)",
"Bash(cat *)",
"Bash(ls *)",
"Bash(echo *)",
"Bash(which *)",
"Bash(pwd)",
"Read(**)",
"Edit(**)"
],
"deny": [
"Bash(rm -rf *)",
"Bash(sudo *)"
]
}
}
This eliminates most prompts for typical development work while blocking the most dangerous patterns.
--dangerously-skip-permissions
For fully automated / headless use (CI, scripts), pass --dangerously-skip-permissions to skip all confirmation prompts:
claude --dangerously-skip-permissions --print "Run the test suite and report failures"
This bypasses ALL confirmation prompts including destructive operations. Only use in sandboxed environments, containers, or CI where the blast radius is bounded. Never in an interactive session on your main machine.
Add the statusLineHook while you're here
The permissions block lives in the same ~/.claude/settings.json as the statusLineHook that powers Headroom's live usage display. If you're editing settings.json to fix permissions, it takes one more line to get your Claude Code session and weekly usage in the menu bar:
{
"statusLineHook": "cat ~/.claude/headroom-usage.json 2>/dev/null | jq -r '"CC \(.sessionUsagePct|floor)%·\(.weeklyUsagePct|floor)%"' 2>/dev/null || echo 'CC --%'",
"permissions": {
"allow": [
"Bash(git *)",
"Bash(npm *)",
"Read(**)",
"Edit(**)"
]
}
}
Headroom reads that file and shows your session (5h) and weekly (7d) usage as a live % in the menu bar — color-coded before a limit stops you.
brew install --cask patwalls/tap/headroom
→ Full settings.json reference
→ statusLineHook setup
→ MCP server configuration