permissions
Declarative permission rules using Claude Code’s exact Tool(specifier)
syntax. Define allow, deny, and ask arrays in any settings.json
to control which tools can run, which paths can be accessed, and which
commands are permitted.
Configuration
Section titled “Configuration”Add a permissions key to any settings file:
// .tallow/settings.json (or .claude/settings.json){ "permissions": { "allow": [ "Bash(npm run *)", "Bash(git commit *)", "Read", "Edit(/src/**/*.ts)" ], "deny": [ "Bash(git push *)", "Read(./.env)", "Edit({home}/.ssh/**)" ], "ask": [ "Bash(docker *)", "Edit(/config/**)" ] }}Rule format
Section titled “Rule format”Rules follow Tool(specifier) or bare Tool (matches all uses):
| Rule | Matches |
|---|---|
Bash(npm *) | Any npm command |
Read | All file reads |
Edit(/src/**/*.ts) | TypeScript files under src/ |
WebFetch(domain:example.com) | Fetch requests to example.com |
mcp__puppeteer__* | All puppeteer MCP tools |
Task(Explore) | The Explore subagent |
Resolution order
Section titled “Resolution order”- Deny — any matching deny rule blocks. Deny always wins.
- Ask — any matching ask rule prompts for confirmation.
- Allow — any matching allow rule permits without prompting.
- Default — no match: permit (denylist-only) or prompt (allowlist mode).
Hardcoded safety denylists (fork bombs, rm -rf /) are never overridable.
Allow / ask / deny outcomes
Section titled “Allow / ask / deny outcomes”Permission checks now return structured reason metadata (reasonCode,
reasonMessage, matchedRule, source scope/path) and surface concise,
actionable messages.
Deny example
Section titled “Deny example”Action denied by permission rule Bash(ssh *).Source: .tallow/settings.json.Hint: Allowed patterns: git *.Ask example
Section titled “Ask example”Reason: Confirmation required by permission rule Bash(docker *).Tool: bashInput: docker compose upRule: Bash(docker *)Allow this action?Redaction behavior
Section titled “Redaction behavior”Sensitive values are redacted before surfacing reasons:
Bash(export API_KEY=[REDACTED])op://[REDACTED]Tool name casing
Section titled “Tool name casing”Both Claude Code and tallow casing work:
| Claude Code | tallow | Both work |
|---|---|---|
Bash | bash | ✓ |
Read | read | ✓ |
Edit | edit | ✓ |
Write | write | ✓ |
WebFetch | web_fetch | ✓ |
Task | subagent | ✓ |
Path patterns
Section titled “Path patterns”Gitignore-style path conventions:
| Pattern | Meaning |
|---|---|
//path | Absolute from filesystem root |
~/path | Relative to home directory |
/path | Relative to settings file location |
./path | Relative to cwd |
Plus tallow’s variable expansion: {cwd}, {home}, {project}.
* matches a single directory level, ** matches recursively.
Shell operator awareness
Section titled “Shell operator awareness”Bash rules split on &&, ||, ;, |, and newlines. Each segment
is evaluated independently:
Bash(git *)deny rule blocksgit status && rm -rf /(second segment fails)Bash(git *)allow rule requires ALL segments to match
Command substitution (` and $()) is fail-closed for allow rules.
Settings precedence
Section titled “Settings precedence”From highest to lowest:
- CLI flags (
--allowedTools,--disallowedTools) - Project local (
.tallow/settings.local.json, trusted projects only) - Project shared (
.tallow/settings.json, trusted projects only) - User (
~/.tallow/settings.json)
.claude/settings.json and .claude/settings.local.json are also
read at their respective project tiers for full Claude Code compatibility.
When project trust is untrusted or stale, .tallow project permission
files are ignored until you run /trust-project.
CLI flags
Section titled “CLI flags”tallow --allowedTools "Bash(npm *)" "Read" --disallowedTools "Bash(ssh *)"Commands
Section titled “Commands”| Command | Description |
|---|---|
/permissions | Show all active rules grouped by source |
/permissions test Bash(docker compose up) | Test a rule against current config |
/permissions reload | Reload config from disk |