Skip to content

Check What Bash(uv run *) Actually Allows Before You Grant It

2026-08-12

An allow rule is a threat model in one line. Before you save Bash(uv run *) so Claude Code can run experiments without prompting, write down the worst command it admits.

Claude Code's permission rules match command text, with * standing in for any text. The guide warns that runners such as npx, devbox run and docker exec execute their arguments as a command, and advises one rule per inner command. uv run is that kind of runner. Bash(uv run *) admits uv run pytest, and it also admits uv run python -c with any code in it. So the rule's scope is "any Python," and on a research machine any Python can read your API keys, upload a dataset or delete a checkpoint. If a benchmark prompt or a scraped page steers the model, that is the set of commands it can reach without asking you.

The pair test. For every allow rule, test the command you meant and its nearest dangerous neighbor. In a disposable repo, save the narrow rule:

json
{ "permissions": { "allow": ["Bash(uv run pytest *)"] } }

Then ask Claude for both:

  1. uv run pytest tests/ -q should run without a prompt;
  2. uv run python -c "print(1)" should prompt you.

If the neighbor runs silently, a broader rule is loaded somewhere. /permissions lists every rule and the settings file it came from. Rules are evaluated deny, then ask, then allow, and the first match decides.

Two more neighbors belong in the pair list. Compound commands are split, so Bash(uv run pytest *) does not approve uv run pytest && rm -rf out/; check that it prompts. And a rule allows a command, not its target: nothing in Bash(git commit *) limits where a > redirect writes.

Write down what the narrow rule still admits, too. pytest imports any conftest.py in the tree, so an agent that can edit files can still run code through uv run pytest. Narrowing shrinks the set; it does not empty it.

Keep the pairs as a regression test, so the next edit to the rule has to pass them. Text rules are one layer, and deny rules have the opposite gap: Bash(curl *) does not stop sh -c 'curl ...'. Measuring what the sandbox adds covers that side, and Chapter 15 explains why bounds on tools have to hold even when the model's judgment does not.

Claude Code documentation.