# Claude Code skill allowed-tools: a one-turn grant, not a sandbox

> Source: <https://dev.to/rulestack/claude-code-skill-allowed-tools-a-one-turn-grant-not-a-sandbox-27p4>
> Published: 2026-08-09 14:49:21+00:00

You add `allowed-tools: Bash(git add *) Bash(git commit *)`

to a skill. You type `/commit`

, and Claude stages and commits without asking. You send one more message, ask for another commit — and the permission prompt is back.

Nothing broke. `allowed-tools`

did exactly what it is specified to do. It is just narrower than the name suggests.

`allowed-tools`

pre-approves the listed tools **for the turn that invokes the skill**. That is the entire scope. The docs are explicit about the expiry: the grant "clears when you send your next message."

Invoking the skill again re-applies it for that turn. So `/commit`

, then a follow-up message asking Claude to commit again, are two different worlds: the first is covered, the second is not.

Two things happen when a skill is invoked, and they have different lifetimes:

| What | Lifetime |
|---|---|
The rendered `SKILL.md` content |
Enters the conversation as a single message and stays for the rest of the session |
The `allowed-tools` grant |
Clears when you send your next message |

Instructions persist. Permissions do not.

That mismatch is why two contradictory-sounding statements are both true: *the skill is still loaded* and *the skill stopped working*. Claude still has the instructions. It just lost the pre-approval, so it asks.

If you hit this, you have two honest options:

This is the part that trips people who read the field as a whitelist:

It does not restrict which tools are available: every tool remains callable, and your permission settings still govern tools that are not listed.

So a skill with `allowed-tools: Read Grep`

has **not** been sandboxed to reading and grepping. Claude can still call Bash. The field only says "these specific ones don't need a prompt right now." Everything else falls back to your normal permission rules.

If you actually want a tool taken away, that is a different field.

`disallowed-tools`

is the restricting half
`disallowed-tools`

removes tools from Claude's available pool while the skill is active. It is the right field for an autonomous skill that should never call something — a background loop that must not stop to ask you a question, for example.

Two limits worth knowing:

`EndConversation`

while any other tool remains.To block a tool across all skills and prompts rather than one skill, use deny rules in your permission settings.

| What you want | Where it goes | How long it lasts |
|---|---|---|
| Skip prompts for a few commands when this skill runs |
`allowed-tools` in `SKILL.md`
|
the invoking turn |
| Keep a tool away from one autonomous skill |
`disallowed-tools` in `SKILL.md`
|
while the skill is active |
| Skip prompts for the whole session | allow rules in permission settings | your settings files |
| Block a tool everywhere | deny rules in permission settings | your settings files |

The rule of thumb: frontmatter is for *this invocation*, settings are for *this project or user*. If you find yourself wanting `allowed-tools`

to stick around, you have discovered that the rule belongs in settings.

Skills can ship their own scripts. The problem is that a hardcoded path breaks the moment the skill is installed somewhere else — and for a plugin skill, `${CLAUDE_SKILL_DIR}`

points at the skill's subdirectory inside the plugin, not the plugin root, so guessing is worse than it looks.

The fix is that Claude Code substitutes `${CLAUDE_SKILL_DIR}`

in **two** places: the skill's markdown content, and Bash rules in `allowed-tools`

. Use the same variable in both and the allow rule matches the exact command the body tells Claude to run:

```
---
name: render-chart
description: Render a chart from a CSV file
allowed-tools: Bash(${CLAUDE_SKILL_DIR}/scripts/render.sh *)
---

Run `${CLAUDE_SKILL_DIR}/scripts/render.sh <csv>` to render the chart.
```

Installed at `~/.claude/skills/render-chart/`

, both occurrences expand to that directory, the rule matches, and the script runs without a prompt. Move the skill and it still matches, because neither side was hardcoded.

There is a sibling variable, `${CLAUDE_PROJECT_DIR}`

, for project-local scripts — that one needs Claude Code v2.1.196 or later.

For skills checked into a project's `.claude/skills/`

directory, `allowed-tools`

takes effect after you accept the workspace trust dialog for that folder — the same as permission rules in `.claude/settings.json`

.

Read that in reverse: **a skill in a repo can grant itself broad tool access**, and clicking "trust" is what turns it on. Reviewing the skills is part of reviewing the repository, not a separate chore.

A 10-second pass before trusting an unfamiliar project:

```
grep -rn "allowed-tools\|disallowed-tools" .claude/skills/ .claude/commands/ 2>/dev/null
```

If a skill you did not write pre-approves `Bash(* )`

or anything close to it, that is worth a second look before you accept the dialog.

`Bash(git commit *)`

covers `git commit -m "x"`

, not a command routed through something else.`disallowed-tools`

.`allowed-tools`

is a receipt you hand over at the door, not a badge you wear all day. It gets you past the prompt for the turn you invoked the skill, and then it is spent. Everything durable — what Claude may do session-wide, what it may never do — lives in your permission settings.

Once you stop expecting the frontmatter to be a sandbox, the field becomes genuinely useful: it removes the friction from the two or three commands a skill actually needs, without quietly widening what Claude can do for the rest of the day.

*Written while building Rulestack — packs of Cursor rules, Claude Code skills, and AGENTS.md setups for people who would rather not rediscover this by trial and error.*

*More notes like this as I find them: @ai-shop.bsky.social on Bluesky.*
