@fgladisch/pi-bash-approval
Interactive allow-list guard for Pi bash tool calls
Package details
Install @fgladisch/pi-bash-approval from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@fgladisch/pi-bash-approval- Package
@fgladisch/pi-bash-approval- Version
0.3.0- Published
- Aug 22, 2026
- Downloads
- 117/mo · 21/wk
- Author
- fgladisch
- License
- MIT
- Types
- package
- Size
- 48.6 KB
- Dependencies
- 0 dependencies · 1 peer
Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
@fgladisch/pi-bash-approval
Guards Pi bash tool calls behind an interactive allow-list. Every bash tool
call is intercepted; commands matching configured patterns run silently, anything
else prompts the user. In non-interactive contexts (pi -p, no UI), unknown
commands are blocked with a reason pointing at the config files.
Install
pi install npm:@fgladisch/pi-bash-approval
Config
The extension reads two files:
- Global settings:
~/.pi/agent/settings.json - Allow-list rules:
~/.pi/agent/.bash-approval
Global settings (settings.json)
splitChains and notifyHerdr live under bashApproval:
{
"bashApproval": {
"splitChains": true,
"notifyHerdr": false
}
}
If missing or malformed, splitChains defaults to true and notifyHerdr
defaults to false.
Allow-list rules (.bash-approval)
One rule per line:
# bash approval allow-list
ls
ls:*
git status:*
npm test:*
Blank lines and # comment lines are ignored.
Pattern syntax (.bash-approval lines)
| Pattern | Matches |
|---|---|
ls |
exact: ls only |
ls:* |
ls exactly, ls <anything> (space-separated) |
git status:* |
git status exactly, git status <anything> |
git* |
trailing-* glob: any command starting with raw git |
The :* form is recommended: it requires an exact prefix plus trailing space, so
git status:* does not match git statusfoo. Bare * is a raw prefix match
and should be used sparingly.
splitChains
Default true: split incoming commands on shell separators (&&, ||, ;,
|, &, newline) and require every segment to match the allow-list.
Example: cd foo && git log only runs unprompted when both segments are
allow-listed. Set false to match the entire command string as one unit.
notifyHerdr
Default false. Set true to emit a herdr:blocked event
({ active: true, label }) while an interactive approval prompt is open, so a
co-installed herdr agent-state extension reports the pane as blocked, and a
matching { active: false } when the prompt resolves. The event is a no-op when
herdr is not installed.
Shell control filtering
When splitChains is true, shell control/declaration segments are ignored so
approval checks focus on actual commands:
- ignored heads include
if,then,elif,else,for,do,done,fi,while,until,case,esac,function - grouping parentheses around subshells are treated as shell syntax, so
(pnpm dev & echo $!)evaluates the innerpnpm devandechocommands - condition tests (
[ ... ],[[ ... ]],test ...) are ignored - assignment-only segments like
FOO=barare ignored - redirection-only segments like
> /tmp/outafter shell groups are ignored - separators inside command substitutions like
$(git ls-files | sort)are not treated as outer command-chain separators - assignment prefixes before commands are stripped, e.g.
FOO=bar npm testevaluates asnpm test - command substitutions inside assignment tokens are evaluated by their inner
command, e.g.
tmp=$(mktemp -d /tmp/foo-XXXXXX)evaluates asmktemp -d /tmp/foo-XXXXXX
Prompt behavior
Unknown interactive commands show options:
- Allow once: allow this command without changing config.
- Allow always (exact):
<command>: append the full trimmed command. - Allow always:
<prefix>:*: append the suggested parameter-aware prefix rule. The suggestion uses the first two tokens when present (git status:*,npm install:*,mkdir -p:*), otherwise the first token (ls:*). The suggestion is derived from the first failing chain segment, not the command head. - Allow always (command):
<command>:*: append command-only prefix rule for the first token of the failing segment (mkdir:*,git:*,npm:*). Hidden when it duplicates the parameter-aware suggestion. - Deny: block with reason
Blocked by user.
Selecting nothing (cancel) is treated as deny. "Allow always" choices persist
immediately to ~/.pi/agent/.bash-approval.
Slash commands
| Command | Action |
|---|---|
/bash-approval-reload |
Re-read ~/.pi/agent/.bash-approval and ~/.pi/agent/settings.json from disk. |
/bash-approval-list |
Show currently allowed bash patterns. |
Inter-extension events
The extension emits lifecycle events on pi.events so another extension can
mirror approvals remotely while the local TUI remains active. For manual
approval requests the first valid local or remote answer wins; when a remote
answer wins, the open local prompt is dismissed. Late remote responses return
{ accepted: false, reason: "already_resolved" }. Listener failures are ignored
so normal bash approval behavior remains the fallback.
| Event | When it fires |
|---|---|
pi-bash-approval:config_loaded |
After startup config load. |
pi-bash-approval:reloaded |
After /bash-approval-reload. |
pi-bash-approval:evaluated |
After allow-list evaluation, including auto-allow. |
pi-bash-approval:request |
Before opening the local approval prompt. |
pi-bash-approval:resolved |
After local or remote approval decision wins. |
pi-bash-approval:rule_persisted |
After an Allow always rule write attempt. |
pi-bash-approval:allowed |
When a command is allowed by allow-list or user choice. |
pi-bash-approval:blocked |
When a command is blocked. |
pi-bash-approval:closed |
When a manual approval request can no longer answer. |
herdr:blocked |
Toggled active around an open prompt (notifyHerdr). |
pi-bash-approval:request includes stable option IDs and a
respond(response) callback. Remote responders can submit
{ source: "remote", action: "allow_once" },
{ source: "remote", action: "allow_always", optionId, rule }, or
{ source: "remote", action: "deny", reason? }.