Permission-Gated Custom Commands¶
Custom commands pre-approve specific tools through an
allowed-toolsfrontmatter list, so listed tools run without prompting — signaling the expected surface, not blocking unlisted tools.
Related lesson: Permissions and Safety Boundaries — this concept features in a hands-on lesson with quizzes.
The default exposure problem¶
Custom commands in Claude Code inherit the session's full tool permissions. A /review-pr command that only reads files and runs git diff still has implicit access to Write, delete, and arbitrary shell — fine for code you wrote yourself, a problem once you share it with a team or run it somewhere unfamiliar.
The allowed-tools frontmatter field "grants permission for the listed tools during the turn that invokes the skill". The docs are explicit about the limit: "It does not restrict which tools are available: every tool remains callable" (Claude Code skills documentation). The grant clears when you send your next message.
Declaring allowed tools¶
Skills (including commands in .claude/commands/) accept YAML frontmatter between --- markers. The allowed-tools field takes a list of tool names Claude may use when the skill is active:
---
name: review-pr
description: Review the current pull request for issues
allowed-tools: Read, Grep, Glob, Bash(git diff *), Bash(git log *)
---
Review the current pull request...
Claude can read files, search with Grep and Glob, and run git diff and git log variants without prompting. Unlisted tools — Write, Edit, arbitrary Bash — still need the same explicit user approval as any tool in a session without an allowlist.
The Bash(git diff *) syntax scopes Bash to commands starting with that prefix. The Claude Code permissions model supports full tool names and prefix-scoped wildcards.
Claude Code applies the same subcommand granularity to its own auto-generated rules: approving a compound command with "Yes, don't ask again" saves one rule per subcommand — up to 5 for one compound command, not a single rule for the whole string (Claude Code permissions docs).
What to include in the allowlist¶
Design the allowlist around the smallest set of tools the command needs:
| Command type | Typical allowlist |
|---|---|
| Code review | Read, Grep, Glob, Bash(git diff *), Bash(git log *) |
| Documentation generation | Read, Glob, Write |
| Dependency audit | Read, Bash(npm list *), Bash(pip list *) |
| Safe exploration | Read, Grep, Glob |
Read, Grep, Glob is a safe baseline for any command that only inspects code. Add Bash only for specific, named subcommands.
Preventing automatic invocation of sensitive commands¶
By default Claude can invoke any skill it judges relevant. For commands with side effects, require explicit invocation instead. Set disable-model-invocation: true:
---
name: generate-release-notes
description: Generate release notes from git history
disable-model-invocation: true
allowed-tools: Read, Bash(git log *), Bash(git tag *)
---
It now runs only when you type /generate-release-notes, and the Claude Code documentation notes this also drops the skill description from Claude's active context. Pairing it with allowed-tools gives the most constrained command mode.
Sharing commands with a team¶
Commands checked into .claude/commands/ (or .claude/skills/<name>/SKILL.md) ship to everyone who clones the repo, and the allowed-tools declaration travels with them. Read that as a grant you inherit, not a default you can trust:
Workspace trust doesn't gate this field. Claude Code applies a project skill's
allowed-toolswhenever you or Claude invoke the skill, including in a-prun in a folder you've never trusted. A skill can grant itself broad tool access, so review theallowed-toolsof skills checked into a repository before you run Claude Code there.
(Claude Code skills documentation)
A shared command removes the per-invocation prompt and adds a per-repository review, done once, before the first run, on somebody else's file.
Layering with session-level permissions¶
Command-level allowed-tools works on top of session-level permissions, not instead of them. Claude Code evaluates permission rules in deny, then ask, then allow order. If a tool is denied at any level, no other level can allow it — the field only narrows what runs without prompting; it cannot grant anything a session-level deny rule blocks.
A PreToolUse hook enforces this even more strictly: exiting with status code 2 blocks the call before permission rules run at all, holding even when the command's allowed-tools list would otherwise let it through without a prompt (Claude Code permissions docs).
When this backfires¶
allowed-tools is a pre-approval mechanism, not a hard restriction. Three failure conditions to account for:
- Unlisted tools still run with one approval. If a prompt injection or rogue model call attempts
Write, the user sees a single approval prompt — the same guard that exists without anyallowed-toolsdeclaration. The allowlist does not add a deny layer; it only removes the prompt for listed tools. - Allowlists go stale. A command that gains new abilities (for example, a
/deployskill that now needsWebFetchto post status) prompts for unlisted tools until you update the allowlist, surprising teams that read "no prompt" as "expected behavior." - A false sense of hard enforcement.
allowed-toolscannot block a tool by itself. Three things can: adisallowed-toolsentry in the same frontmatter, which lists "tools removed from Claude's available pool while this skill is active" (skills documentation); a session-level deny rule; or a PreToolUse hook.
Key Takeaways¶
allowed-toolsin command frontmatter pre-approves a named subset of tools — they run without prompting during that command's execution.- Unlisted tools are not blocked; they require the same user approval as any tool in a session without an allowlist.
- The
Bash(prefix *)syntax scopes bash access to specific subcommands rather than all shell execution. disable-model-invocation: trueprevents Claude from triggering a command automatically — use this for any command with side effects, even conservative ones.- Read the
allowed-toolsof any skill you did not write before running Claude Code in its repository. Workspace trust does not gate the field, and a skill can grant itself broad access. - Session-level deny rules take precedence over
allowed-tools; the field narrows the no-prompt set but cannot expand session permissions.