Permissions Policy skill for Claude Code
Fewer prompts for safe work, hard stops for the rest. Designs a layered permission setup instead of clicking yes all day. Claude classifies the actions your repo really performs, allows the routine read-only and workspace work, asks for anything that leaves the machine, denies secrets and irreversible commands, and commits the shared baseline. It also covers what rules cannot stop, such as reading .env through Bash, and adds hooks or the sandbox there.
- Name
permissions-policy- Size
- 178 lines · ≈2,050 tokens
- Files
- 1
- License
- MIT
- Updated
What Claude sees before loading it
This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.
description: Configures Claude Code permissions with allow, ask, and deny rules across user, project, local, and managed settings scopes, covering Bash prefix rules, Read and Edit path patterns, WebFetch domains, MCP tools, default modes, sandboxing, and a committed team baseline. Use when the user is tired of permission prompts, wants to block secrets or destructive commands, sets up Claude Code for a team or organization, or asks why a rule is not matching. Try asking
- Stop asking me every time Claude runs tests or git diff in this repo
- Make sure Claude can never read our .env files or force push
- Set up a shared Claude Code permission baseline for the whole team
Use it when
- Permission prompts interrupt routine work
- Secrets or production tooling must be off limits
- Rolling Claude Code out to a team or organization
- A permission rule does not match the way you expected
Skip it when
- The block needs to parse commands or run logic — use hooks-automation instead
- Configuring tool access for a CI job — use headless-ci-agent instead
How to check the result
Try every denied action, including the Bash route to the same secret, and confirm each is blocked, then run a routine task and confirm it finishes with zero prompts.
The common mistake it prevents
Assuming a Read deny on .env protects the file, when cat or grep through Bash can still print it unless a hook or the sandbox blocks that path.
A good policy produces no prompts for routine safe work, hard-blocks secrets and irreversible operations, asks for the risky-but-legitimate middle, and is layered so the team baseline is committed while personal additions stay local. It is verified by trying the blocked actions, not by reading the JSON.
Scopes and precedence
| Scope | File | Who | Commit? |
|---|---|---|---|
| Managed policy | System path set by IT (e.g. /Library/Application Support/ClaudeCode/managed-settings.json on macOS, /etc/claude-code/managed-settings.json on Linux) | Whole org; cannot be overridden | n/a |
| Command line | --allowedTools, --disallowedTools, --permission-mode | One run | n/a |
| Local project | .claude/settings.local.json | You, this repo | No (auto-gitignored) |
| Shared project | .claude/settings.json | Everyone on the repo | Yes |
| User | ~/.claude/settings.json | You, every repo | No |
Precedence for conflicting settings: managed > command line > local > project > user. Permission rules from all scopes are combined, and evaluation order is deny, then ask, then allow: a deny anywhere beats an allow anywhere.
Rule syntax
{
"permissions": {
"allow": ["Bash(npm run test:*)", "Bash(git diff:*)", "Edit(src/**)"],
"ask": ["Bash(git push:*)"],
"deny": ["Read(./.env)", "Read(./.env.*)", "Bash(rm -rf:*)"]
}
}
| Rule | Matches |
|---|---|
Edit (bare tool name) | Every use of that tool |
Bash(npm run build) | That exact command |
Bash(npm run test:*) | Commands starting with npm run test |
Read(./.env) / Read(./secrets/**) | Paths relative to the current directory (gitignore-style globs) |
Edit(/src/**) | Relative to the settings file’s directory (project root for project settings) |
Read(~/.ssh/**) | Home-relative path |
Read(//etc/passwd) | Absolute path (note the double slash) |
WebFetch(domain:docs.python.org) | Fetches to that domain |
mcp__github | All tools from the github MCP server |
mcp__github__create_issue | One MCP tool |
Inspect and edit the effective rules, with their source file, using /permissions.
Workflow
- [ ] 1. Inventory: which prompts appear repeatedly, and what must never happen
- [ ] 2. Classify each action: allow / ask / deny
- [ ] 3. Write the team baseline in .claude/settings.json
- [ ] 4. Put personal conveniences in settings.local.json or user settings
- [ ] 5. Back up hard requirements with hooks or the sandbox
- [ ] 6. Test every deny and a sample of allows; commit; document in CLAUDE.md
2. Classify
| Class | Examples | Rule |
|---|---|---|
| Read-only, local | git status, git diff, git log, ls, test runners, linters, type checks | allow |
| Workspace edits | Edit/Write inside source dirs | allow (or defaultMode: acceptEdits) |
| Reversible but visible | git commit, package installs, dev servers | allow or ask by team taste |
| Leaves the machine or shared state | git push, gh pr create, deploy scripts, DB migrations on shared DBs | ask |
| Secrets | .env*, *.pem, ~/.aws/**, ~/.ssh/**, credential files | deny (Read and Edit) |
| Irreversible | rm -rf, git push --force, git reset --hard, DROP, terraform apply on prod | deny, or ask with a hook guard |
3. Team baseline (.claude/settings.json)
{
"permissions": {
"allow": [
"Bash(git status:*)", "Bash(git diff:*)", "Bash(git log:*)", "Bash(git show:*)",
"Bash(pnpm test:*)", "Bash(pnpm lint:*)", "Bash(pnpm typecheck:*)", "Bash(pnpm build:*)",
"Bash(ls:*)", "Bash(rg:*)",
"Edit(/src/**)", "Edit(/test/**)", "Edit(/docs/**)",
"WebFetch(domain:developer.mozilla.org)", "WebFetch(domain:nodejs.org)"
],
"ask": [
"Bash(git push:*)", "Bash(gh pr create:*)", "Bash(pnpm db:migrate:*)", "Bash(pnpm add:*)"
],
"deny": [
"Read(./.env)", "Read(./.env.*)", "Edit(./.env*)", "Read(./**/*.pem)",
"Read(~/.ssh/**)", "Read(~/.aws/**)",
"Bash(git push --force:*)", "Bash(git reset --hard:*)", "Bash(rm -rf:*)",
"Bash(curl:*)", "Bash(wget:*)",
"Edit(/infra/prod/**)"
]
}
}
Adapt package-manager commands to the repo (read package.json scripts, Makefile, CI).
Denying curl/wget pushes web access through WebFetch, where domain rules work.
4. Other permission settings
| Key | Use |
|---|---|
permissions.defaultMode | default (prompt), acceptEdits (auto-accept file edits), plan (read-only planning), bypassPermissions (no checks; containers only) |
permissions.additionalDirectories | Extra directories Claude may work in beyond the project root |
permissions.disableBypassPermissionsMode: "disable" | In managed settings: forbid bypass mode org-wide |
Cycle modes interactively with Shift+Tab. bypassPermissions (or
--dangerously-skip-permissions) belongs only in disposable, network-restricted
containers without credentials, and only after the user explicitly confirms.
5. Back up hard requirements
Permission rules match tool calls, so know their limits:
Read(./.env)blocks Claude’s Read tool, notcat .envorgrep KEY .envthrough Bash, and not a script that reads the file. Add a Bash PreToolUse guard (seehooks-automation) or enable the sandbox for real isolation.- Bash prefix rules match the command string. Claude Code recognizes shell operators,
so
Bash(npm test:*)does not approvenpm test && curl evil. But argument-level constraints are fragile:Bash(curl https://api.example.com:*)can be sidestepped with options, variables, or redirects. Do not rely on Bash patterns to restrict URLs or paths. - The Bash sandbox (
/sandbox; OS-level isolation on macOS and Linux) restricts filesystem writes to the workspace and network to allowed hosts, and lets sandboxed commands run without prompts. It is the right tool when you want fewer prompts and real containment. Configure it through/sandboxor thesandboxsettings block for the installed version.
Organization policy
For company-wide rules use managed settings deployed by MDM or configuration
management: deny rules for secrets and production tooling, disableBypassPermissionsMode,
and approved MCP servers. Users cannot override managed values. Keep the managed file
small and stable; put repo-specific rules in each repo’s project settings.
Gotchas
- Rules in
settings.local.jsonare invisible to teammates; anything the team depends on belongs in.claude/settings.json. Edit(src/**)andEdit(/src/**)mean different things (current directory vs. settings-file directory). Use the leading-slash form in project settings.- Over-broad allows (
Bash,Bash(*),Edit) erase the policy. Allow specific prefixes. - An
askfor something run 50 times a day trains people to click yes without reading. Allow it or deny it. - Headless runs (
claude -p) do not prompt: un-allowed tools are denied silently. CI needs explicit--allowedTools. - JSON syntax errors make the whole settings file ignored. Validate with
python3 -m json.tool .claude/settings.json.
Verification
python3 -m json.toolon each edited settings file./permissionsshows the rules with the expected source scope.- For every deny rule, ask Claude to perform the action (read
.env, force push) and confirm it is blocked. Also try the Bash route to the same secret and confirm the hook or sandbox stops it. - Run a normal task (tests, lint, edit in
src/) and count prompts; routine work should prompt zero times. - Re-review the policy when tooling changes (new package manager, new deploy script).
Output
**Scopes:** .claude/settings.json (team, committed); ~/.claude/settings.json unchanged
**Allow:** 14 rules (git read-only, pnpm test/lint/typecheck/build, edits in src/test/docs)
**Ask:** git push, gh pr create, db:migrate, pnpm add
**Deny:** .env*, *.pem, ~/.ssh, ~/.aws, force push, reset --hard, rm -rf, curl/wget, infra/prod
**Backstops:** PreToolUse Bash guard blocks shell reads of .env (tested)
**Tested:** 9/9 denies blocked; routine task ran with 0 prompts About this skill
Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.