Skip to content

Claude Code permissions

Tool-usage permission rules for Claude Code sessions, configured in .claude/settings.json (project-level, version-controlled) alongside the hooks. Distinct from hooks: permissions gate whether a tool call is allowed to run at all — allow / ask / deny — rather than running a command before/after it.

Precedence

deny wins over ask, and ask wins over allow — regardless of which settings file (user, project, or local) a rule came from, and regardless of how specific a competing rule is. A broad allow cannot be carved back open by a narrower rule elsewhere; only an equally-or-more-specific deny/ask rule changes the outcome for a given match.

Settings load in order user → project → local, later sources layering on top, but for permission rules specifically the deny/ask/allow category always decides first — see Claude Code settings docs for the general load order.

Example rules (.claude/settings.json)

json
{
  "permissions": {
    "allow": ["Read", "Grep", "Glob"],
    "ask": ["Bash(vercel *)", "Bash(pnpm run deploy)", "Bash(pnpm deploy*)"],
    "deny": [
      "Bash(cat .env*)",
      "Bash(cat **/.env*)",
      "Bash(less .env*)",
      "Bash(more .env*)",
      "Bash(head .env*)",
      "Bash(tail .env*)",
      "Bash(printenv)",
      "Bash(env)",
      "Read(.env)",
      "Read(.env.local)",
      "Read(.env.production)",
      "Read(.env.development)",
      "Read(.env.test)"
    ]
  }
}

Add nested package env paths (e.g. Read(apps/cms/.env)) to deny when those files hold secrets.

Read-only inspection — allow

Read, Grep, Glob never prompt. Matches a general stance that inspection is safe; Edit/Write/Bash still go through Claude Code's normal interactive approval unless you override that default.

Deploy commands — ask

vercel *, pnpm run deploy, pnpm deploy* always prompt for confirmation, even when a personal .claude/settings.local.json (gitignored) has a broad Bash(pnpm run *) allow rule that would otherwise silently cover pnpm run deploy. Because ask rules win over allow rules from any source, this closes that gap without touching the personal file.

This is Claude Code's counterpart to the Allow/Deny dialog Grok hooks enforce before production deploy. The shell PreToolUse hook still matters so prefix variations and chained commands are caught, not only literal permission-rule prefixes.

Secret exposure — deny

Two independent protections, matched at the level of each tool's own permission-rule syntax (there's no rule that spans both Bash and Read in one pattern):

  • Bash: cat/less/more/head/tail against .env*, plus bare printenv/env, are hard-blocked — no prompt, just refused. A .env* wildcard also blocks reading .env.example via those shell commands (harmless content; use the Read tool for templates instead).
  • Read: explicit secret filenames only — .env, .env.local, production/dev/test variants — deliberately not a .env* wildcard. A wildcard would also catch .env.example (the git-tracked, secret-free template), and since deny always wins over allow with no specificity override, there'd be no way to carve the example files back open. New env-file variants not in the list aren't automatically covered — add them when introduced.

Troubleshooting

SymptomCheck
Agent still ran vercel --prod without askingConfirm .claude/settings.json is present and hasn't been locally overridden with a denyallow downgrade in a higher-precedence source
Agent can't read .env.exampleCheck it isn't being read via a denied Bash command (cat/less/etc.) — use the Read tool instead
A new .env.* variant is readable when it shouldn't beAdd its exact filename to permissions.deny for both the Bash(cat ...) family and Read(...)

Frontend Corner — agent ops, tooling, and decision records