Skip to the content.

Permissions Quick Reference

Rules live in .claude/permissions.yaml (project) or ~/.claude/permissions.yaml (global). You can also split rules across .claude/permissions.d/*.yaml (or ~/.claude/permissions.d/*.yaml) — each drop-in file is loaded as its own layer; deny in any file wins. Run /reload-plugins after edits.


Decisions

decide: allow | ask | deny | abstain – strictest wins: deny > ask > allow > abstain

Patterns

AND vs OR

List form

Any rule section can be written as a list to apply multiple rules in order:

write:
  - path-in: ["**/.env*", "~/.ssh/*"]
    decide: deny
  - decide: allow   # catch-all

List form can also be used under nested rules:

bash:
  git:
    - push:
        decide: deny
    - add:
        decide: ask
    - decide: deny    # catch-all: matches any git command not matched above
      reason: No other git commands

Command descriptors

Place YAML files in ~/.claude/permissions.d/commands/<command>.yaml (global) or .claude/permissions.d/commands/<command>.yaml (project). The project layer wins on conflict. Without a descriptor, all flags default to arity 0 (boolean) and no positionals are typed as paths.

# .claude/permissions.d/commands/kubectl.yaml
kubectl:
  description: Kubernetes CLI
  flags:
    context:
      arity: 1      # consumes next token as value
      kind: string
    n|namespace:
      arity: 1
      kind: string
  positionals:
    - kind: string   # first positional (subcommand)
    - kind: path
      variadic: true # remaining positionals are paths

Flag arity: 1 means the flag takes a value; arity: 0 means it is boolean. Use short|long to declare both forms together.


Bash

Keys nest: bash > command > subcommand. Each rule level consumes one positional word from the command line. cmd inside a nested rule addresses args after the subcommand word.

bash:
  sudo:
    decide: deny

  git:
    status: { decide: allow }
    push:
      decide: ask
      reason: Confirm push

  docker:
    compose:
      build:
        decide: ask   # cmd here matches args after "build", not "compose" or "build"

Bash fields

All fields in a rule are AND’d.

Field Semantics
cmd: "src/** dist/**" Positional args matched by index; space-separated or array; AND
cmd-in: [A, B] Any positional arg matches any entry; OR
options: [r\|recursive, f\|force] All flags present; x\|long matches either short or long form; AND
options-in: [force, force-with-lease] Any flag present; OR
options: {m\|message: "/wip/"} Flag with specific value
env: {CI: "true"} All env vars match; AND
cwd: $/** cwd matches pattern; $/** = anywhere inside this project
cwd-in: [/etc/**, /usr/**] cwd matches any entry; OR
file: {"~/.kube/config": true} File exists
file: {"~/.kube/config": {contains: "current-context: sandbox"}} File exists and contains pattern

not:

not: inverts any combination of fields and works in any rule type. The example below denies all aws commands when AWS_PROFILE is anything other than sandbox:

bash:
  aws:
    - not:
        env:
          AWS_PROFILE: sandbox
      decide: deny
      reason: Blocked outside sandbox

File tools (read, write, edit, multi_edit)

Fields: path (single pattern) or path-in (OR list). All other fields (cwd, env, not, etc.) also apply.

read:
  path: "**/.env*"
  decide: ask

write:
  - path-in: ["**/.env*", "~/.ssh/*"]
    decide: deny
  - decide: allow   # catch-all

WebFetch

Fields: host (single pattern) or host-in (OR list).

webfetch:
  - host-in: [docs.anthropic.com, "*.github.com"]
    decide: allow
  - decide: ask

Tool-name rules

The YAML key is a glob matched against the Claude Code tool name. Quote keys containing glob chars. Use tool-in to list exact names under a readable label:

ToolSearch:
  decide: allow

"mcp__*__delete_*":
  decide: deny

github-writes:
  tool-in: [mcp__github__create_issue, mcp__github__create_pull_request]
  decide: ask

Nested rules

rules: groups sub-rules under shared parent conditions. The parent needs no decide – it is a pure filter:

bash:
  aws:
    - env:
        AWS_PROFILE: /^(?!sandbox$)/
      rules:
        - cmd: "* delete-*"
          decide: deny
        - decide: ask
          reason: Confirm on non-sandbox profile

Troubleshooting