DeepDojo.AIPublished 2026-11-12中文

Why allowed-tools Doesn't Make a Skill Read-Only

Claude CodeAI Coding

You give a read-only review Skill allowed-tools: [Read] and assume it can only read files now. Then, during a run, it rewrites a config file through Bash. You check the frontmatter: Read is there, spelled correctly.

The catch is that allowed-tools means the opposite of what it sounds like. It says "these tools can be used without a prompt this turn," not "only these tools can be used." The other tools are still there and go through the normal permission prompts; if the session has already loosened permissions, they simply run.

To give a Skill a real boundary, keep three settings apart:

  • allowed-tools: which calls can skip the prompt this turn;
  • disallowed-tools: which tools are removed from the list this turn;
  • permissions.deny: the long-term floor that ordinary conversation and every Skill must respect.

Read-only takes all three layers. When you bring in someone else's Skill, look in a few more places: which tools it allows, whether its body runs shell commands automatically, what its bundled scripts touch, and which Hooks it registers. A read-only review Skill shows how each piece fits.

01 The Frontmatter of a Read-Only Review Skill

SKILL.md starts with YAML configuration between two --- lines, called frontmatter. This review Skill uses it to limit tool access:

---
name: Review Changes
description: Review code changes on the current branch against a specified baseline for correctness, security, and regression risk. Use when the user asks to review a diff, commit, or branch.
argument-hint: "<base-ref>"
arguments:
  - base
disable-model-invocation: true
context: fork
agent: general-purpose
allowed-tools:
  - Read
  - Grep
  - Glob
  - Bash(git status)
  - Bash(git diff *)
disallowed-tools:
  - Edit
  - Write
---

Review the current workspace against `$base`. Analyze read-only; do not modify files.

1. If `$base` is missing, cannot resolve to a valid Git reference, or this is not a Git repository, explain and stop. Do not guess a baseline.
2. Run `git status`, inspect the diff against `$base`, and read affected implementation and tests. Ordinary diffs omit untracked files; list them separately as unreviewed scope.
3. Report only issues with actual behavioral impact and traceable evidence.
4. Each finding includes severity, file and line, trigger, impact, and the smallest viable fix.
5. If there are no findings, state the checked scope and remaining unverified risks.

Return only the review; do not create commits or edit code.

It uses both an allow field and a removal field. context: fork hands the review to a separate subagent; what to watch in that handoff is covered in briefing a subagent and checking its findings.

02 Three Tool Settings, Three Layers

allowed-tools grants prompt-free use for the current turn and doesn't remove other tools. allowed-tools: [Read] alone can't create a read-only boundary. Keep the three apart:

allowed-tools: calls that can skip the prompt in the current turn
disallowed-tools: tools forcibly removed from the list in the current turn
permissions.deny: a long-term floor that ordinary conversation and every Skill must respect

The Claude Code skills documentation defines allowed-tools as tools Claude can use without asking during the turn that invokes the skill; the grant clears when you send your next message. disallowed-tools are tools removed from Claude's available pool while the skill is active.

Now think about the read-only reviewer role. Granting only Bash(git diff *) comes with a clear reason; granting all of Bash adds a lot of unrelated power. Even with Edit and Write removed, Bash can still write files. Manual invocation only decides when the Skill starts; the actual command range has to come from session permissions. For tightening Bash rules, see 5 common gaps in Claude Code permission rules.

The allowed-tools: [Read] Skill from the opening was missing the last two layers: it never removed the write tools, and session permissions never limited Bash.

03 Check Four Places Before Adopting an Outside Skill

Before bringing in an outside Skill, check four places:

  • which Bash, MCP, and write tools the frontmatter allows;
  • whether the body uses shell preprocessing;
  • what scripts/ accesses;
  • which events its Skill Hooks run on.

Even before you trust a workspace, a Skill's allowed-tools may still take effect when it's invoked, so review that grant separately. The docs call this out directly: workspace trust doesn't gate the field, and Claude Code applies a project skill's allowed-tools even in a -p run in a folder you've never trusted. A skill can grant itself broad tool access, so review the allowed-tools of skills checked into a repository before you run Claude Code there.

04 Dynamic Commands Run Before the Model Sees the Body

A Skill can include dynamic commands like !`git status --short` in its body; they run at load time and their output is inserted. These commands run before the model sees the Skill, so the model can't decide to skip them. When reviewing an outside Skill, read dynamic commands as carefully as scripts.

The docs add a few execution details:

  • injected commands are checked against your permission rules, and one that matches a deny rule aborts the whole invocation;
  • if any command fails, the entire invocation aborts, and Claude never sees that run's Skill content;
  • an organization can turn off this preprocessing with disableSkillShellExecution, which replaces each command with a "disabled by policy" notice instead of running it. Per the settings reference, a true in managed settings can't be overridden by false elsewhere.

Dynamic commands should be read-only, fast, capped in output, and kept away from secrets and unrelated data. A shared Skill should state the shell, commands, and local environment it needs, and what happens when a command fails. What to inject and how much is covered in how to split a long SKILL.md file.

05 Where paths and Hooks Stop

A Skill's paths limits when it applies automatically, not which files it can access. paths: ["db/**"] doesn't guarantee a migration Skill stays inside db/. The official field reference only says it limits when the skill is activated: when set, Claude loads the skill automatically only when working with matching files. Actual reads and writes are still governed by permissions and tool boundaries.

A Skill's Hooks run from invocation until the session ends; finishing one reply doesn't clear them. The docs say Claude Code registers them when the skill is invoked and keeps running them for the rest of the session, including turns after the skill's own. A check that only serves this workflow should say when it no longer applies; a control every task needs belongs in project Settings.

If the Skill depends on a particular shell, model, command, or tool, declare the prerequisites and failure behavior so a shared Skill doesn't press on with steps that don't fit another environment.

Skipping the prompt, removing a tool, and denying long-term are three different layers. Look at all three when setting a Skill's read-only boundary, check all four places before adopting someone else's Skill, and the "read-only Skill that edited a file" from the opening has a clear explanation and a fix.

Claude Code Best Practices →

Explain how prompt-free grants, removed tools, and long-term denies differ in a Skill config, and give it a real read-only boundary.

RSS