Appearance
Pi Permissions
Configure Pi tool permissions via the pi-guardrails extension (forked from @aliou/pi-guardrails to fix RPC mode) and ~/.pi/agent/extensions/guardrails.json.
Permission Model
Pi allows all tool calls by default — no prompts, no gates. The guardrails extension adds gates for specific dangerous operations. Two tiers:
Confirm — Operations where uncommitted or committed work could be permanently lost. Prompts for confirmation in interactive mode, blocks in headless/RPC mode. The test: "if this runs at the wrong time, can work disappear?" If yes, it belongs here.
Deny — Catastrophic operations that should never run under any circumstances. Blocked silently, no prompt.
Everything else is auto-allowed. There is no explicit allow list because Pi doesn't prompt by default — there's nothing to suppress.
Comparison to Claude Code
Claude Code starts locked down (everything prompts unless allowed). Pi starts open (everything runs unless blocked). The guardrails extension bridges the gap by adding confirmation prompts for dangerous commands.
| Claude Code | Pi equivalent |
|---|---|
| Allow list (auto-approve) | Default behavior (no config) |
| Ask list (prompt) | permissionGate.patterns (confirm) |
| Deny list (block) | permissionGate.autoDenyPatterns (block) |
| File protection | policies.rules (noAccess/readOnly) |
Install
sh
pi install git:github.com/chriscalo/pi-guardrailsThis persists to ~/.pi/agent/settings.json and loads in every session automatically. The only bypass is pi --no-extensions.
Structure
Config lives at ~/.pi/agent/extensions/guardrails.json (global) or .pi/extensions/guardrails.json (project-local). The extension merges all scopes: global → project → memory (session). Later scopes win.
json
{
"permissionGate": {
"patterns": [
{ "pattern": "...", "description": "..." }
],
"autoDenyPatterns": [
{ "pattern": "..." }
],
"allowedPatterns": [
{ "pattern": "..." }
]
},
"policies": {
"rules": [
{
"id": "...",
"patterns": [{ "pattern": "..." }],
"protection": "noAccess"
}
]
}
}Patterns use substring matching by default. Add "regex": true for regex matching. Permission gate patterns are checked against bash commands. Policy patterns are checked against file paths.
Built-in defaults (always active)
The extension ships with these. No configuration needed — they're always on unless explicitly disabled.
Built-in: dangerous command detection
Matched structurally via AST parsing (not regex) for fewer false positives. Prompts for confirmation in interactive mode, blocks in headless/RPC mode.
rm -rf — recursive force delete
sudo — superuser command
dd if= — disk write operation
mkfs. — filesystem format
chmod -R 777 — insecure recursive permissions
chown -R — recursive ownership changeBuilt-in: secret file protection
Blocks read, write, edit, bash, grep, find, and ls on matching paths. The agent sees a block message instead of the file contents.
.env — blocked (noAccess)
.env.local — blocked
.env.production — blocked
.env.prod — blocked
.dev.vars — blocked
.env.example — allowed (exception)
.env.sample — allowed
.env.test — allowedConfirm (prompt for confirmation)
Custom patterns added to permissionGate.patterns. These extend the built-in list — they don't replace it.
Confirm: git operations that rewrite remote history
Force push rewrites the remote branch. If others have based work on commits that get replaced, their work can be silently lost.
git push --force — force push (long flag)
git push -f — force push (short flag)Confirm: git operations that discard uncommitted changes
Edits that exist nowhere in git history are permanently lost.
git reset --hard — discard staged and unstaged changes
git checkout -- — overwrite working tree files
git clean — delete untracked files (never committed)Confirm: git operations that delete branches, tags, worktrees
Refs and directories are permanently gone. Unmerged branch commits survive in the reflog temporarily, but tags and worktree directories do not.
git branch -d — delete branch
git branch -D — force-delete branch
git branch --delete — delete branch (long flag)
git tag -d — delete tag
git tag --delete — delete tag (long flag)
git worktree remove — delete a worktree directoryConfirm: git operations that destroy stash entries
Stash entries are saved work. drop and clear delete them permanently, and pop deletes the entry after applying.
git stash drop — delete a single stash entry
git stash pop — apply and delete a stash entry
git stash clear — delete all stash entriesConfirm: GitHub CLI destructive and write API operations
Can delete repos and releases, or mutate remote state via raw API calls.
gh repo delete — permanently delete a repository
gh release delete — delete a release
gh api -X DELETE — raw API DELETE request
gh api -X POST — raw API POST request
gh api -X PUT — raw API PUT request
gh api -X PATCH — raw API PATCH request
gh api --method DELETE — raw API DELETE (long flag)
gh api --method POST — raw API POST (long flag)
gh api --method PUT — raw API PUT (long flag)
gh api --method PATCH — raw API PATCH (long flag)Confirm: file mutations that bypass git
In-place edits bypass git's safety net.
sed -i — edit file in place (short flag)
sed --in-place — edit file in place (long flag)Deny (block entirely)
Custom patterns added to permissionGate.autoDenyPatterns. Blocked silently — no prompt, no option to override.
rm -rf / — destroy entire filesystemFile policies
Custom rules added to policies.rules. Each rule has a unique id for deduplication across config scopes.
SSH keys and configuration
json
{
"id": "ssh-keys",
"description": "SSH private keys and configuration",
"patterns": [
{ "pattern": ".ssh/" },
{ "pattern": "id_rsa" },
{ "pattern": "id_ed25519" },
{ "pattern": "id_ecdsa" }
],
"allowedPatterns": [
{ "pattern": "*.pub" }
],
"protection": "noAccess",
"onlyIfExists": true,
"blockMessage": "Accessing {file} is not allowed. This file contains SSH keys."
}Policy protection levels:
noAccess— blocks read, write, edit, bash, grep, find, lsreadOnly— blocks write, edit, bash (allows reading)none— explicit no protection (override a higher-scope rule)
When multiple rules match the same file, strongest protection wins: noAccess > readOnly > none.
What's NOT gated (deliberate)
Claude Code's ask list includes general-purpose scripting commands (python3, bash, sh, xargs, npx, npm exec, volta). These are not added to Pi's guardrails because the permission models are inverted:
- Claude Code prompts on everything by default, so scripting commands need to be in the ask list to get a prompt instead of being silently blocked.
- Pi allows everything by default. Adding
python3orbashto the dangerous patterns list would prompt on every script execution — too noisy for the open-by-default model.
If a script does something dangerous (e.g., python3 -c "import os; os.system('rm -rf /')") the guardrails extension catches the dangerous part via compound command analysis.
Confirmation UX
When a dangerous command is detected in interactive mode:
- y/Enter — allow this once
- a — allow for the rest of this session (saved in memory scope, forgotten when Pi exits)
- n/Esc — deny (block the command)
In headless/RPC mode (no UI), dangerous commands are blocked by default — no way to confirm. This is the safe fallback for dashboard-spawned sessions.
Optional: enable LLM-powered command explanation in the confirmation dialog:
json
{
"permissionGate": {
"explainCommands": true,
"explainModel": "anthropic/claude-haiku-4-5",
"explainTimeout": 5000
}
}Full config file
The complete ~/.pi/agent/extensions/guardrails.json with all custom patterns from the sections above:
json
{
"permissionGate": {
"patterns": [
{
"pattern": "git push --force",
"description": "force push (rewrites remote history)"
},
{
"pattern": "git push -f",
"description": "force push (rewrites remote history)"
},
{
"pattern": "git reset --hard",
"description": "discard all uncommitted changes"
},
{
"pattern": "git checkout --",
"description": "overwrite working tree files"
},
{
"pattern": "git clean",
"description": "delete untracked files"
},
{
"pattern": "git stash drop",
"description": "delete stash entry"
},
{
"pattern": "git stash pop",
"description": "apply and delete stash entry"
},
{
"pattern": "git stash clear",
"description": "delete all stash entries"
},
{
"pattern": "git branch -d",
"description": "delete branch"
},
{
"pattern": "git branch -D",
"description": "force-delete branch"
},
{
"pattern": "git branch --delete",
"description": "delete branch"
},
{
"pattern": "git tag -d",
"description": "delete tag"
},
{
"pattern": "git tag --delete",
"description": "delete tag"
},
{
"pattern": "git worktree remove",
"description": "delete worktree directory"
},
{
"pattern": "gh repo delete",
"description": "permanently delete a GitHub repository"
},
{
"pattern": "gh release delete",
"description": "delete a GitHub release"
},
{
"pattern": "gh api -X DELETE",
"description": "GitHub API DELETE request"
},
{
"pattern": "gh api -X POST",
"description": "GitHub API POST request"
},
{
"pattern": "gh api -X PUT",
"description": "GitHub API PUT request"
},
{
"pattern": "gh api -X PATCH",
"description": "GitHub API PATCH request"
},
{
"pattern": "gh api --method DELETE",
"description": "GitHub API DELETE request"
},
{
"pattern": "gh api --method POST",
"description": "GitHub API POST request"
},
{
"pattern": "gh api --method PUT",
"description": "GitHub API PUT request"
},
{
"pattern": "gh api --method PATCH",
"description": "GitHub API PATCH request"
},
{
"pattern": "sed -i",
"description": "in-place file edit (bypasses git)"
},
{
"pattern": "sed --in-place",
"description": "in-place file edit (bypasses git)"
}
],
"autoDenyPatterns": [
{ "pattern": "rm -rf /" }
]
},
"policies": {
"rules": [
{
"id": "ssh-keys",
"description": "SSH private keys and configuration",
"patterns": [
{ "pattern": ".ssh/" },
{ "pattern": "id_rsa" },
{ "pattern": "id_ed25519" },
{ "pattern": "id_ecdsa" }
],
"allowedPatterns": [
{ "pattern": "*.pub" }
],
"protection": "noAccess",
"onlyIfExists": true,
"blockMessage": "Accessing {file} is not allowed. This file contains SSH keys."
}
]
}
}Sync Permissions
To sync this config to a machine:
- Install the extension:
pi install git:github.com/chriscalo/pi-guardrails - Copy the JSON above to
~/.pi/agent/extensions/guardrails.json - Verify by starting a Pi session and running a gated command (e.g.,
git push --force) — it should prompt for confirmation.
To check current config interactively: /guardrails:settings
Managing config
sh
# Interactive settings editor
/guardrails:settings
# Add a policy rule with AI assistance
/guardrails:add-policySource reference
- Fork:
github.com/chriscalo/pi-guardrails(forked from@aliou/pi-guardrailsv0.9.5). Replacedctx.ui.custom()withctx.ui.select()so the confirmation dialog works in both TUI and RPC mode. Upstream bug: aliou/pi-guardrails#19. - Extension source:
src/hooks/permission-gate.ts - Config schema:
src/config.ts - File policies:
src/hooks/policies.ts - Pi
tool_callevent:pi-coding-agent/dist/core/extensions/types.d.ts—ToolCallEvent,ToolCallEventResult({ block, reason })