Skip to content

Latest commit

 

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pi-execution-guard

A pi extension that puts a guard in front of test and validation commands and selected state-changing Git operations. Uncommitted changes get one extra layer: worktree-discard Git operations (checkout with a pathspec, restore, reset --hard, clean, stash pop/apply/drop, switch --discard-changes) ask you before the first run, not only on repeats.

It watches calls to pi's built-in bash tool. Commands typed directly by the user with ! are not intercepted.

Language coverage

  • Rust (cargo) — full coverage: cargo test, cargo nextest run, cargo check, cargo clippy, cargo fmt --check require a timeout and cannot run in parallel; workspace-wide runs need one-time approval; plain cargo fmt counts as a state change. Workspace approval is tuned to Cargo: in a virtual workspace (root Cargo.toml with [workspace] and no [package]) plain cargo test needs approval; in a single-crate repository it is allowed. To test one crate, use cargo test -p <crate>.
  • JavaScript / TypeScriptnpm test, pnpm test, yarn test, bun test, pnpm run test, npx vitest run, npx jest, vitest run, tsc --noEmit, eslint. Timeout and repeat rules apply; no workspace-approval concept.
  • Pythonpytest, mypy. Same rules as above.
  • Any language — high-risk Git protection and recursive-delete protection (below) work regardless of project type.
  • npm run build, npm install, bare vitest, scripts whose name merely contains test, and anything inside quoted strings are not treated as validation commands.

How commands are classified

The command is split into segments at &&, ||, ;, and newlines, and every segment is classified by its leading program name only. A git reset inside a commit message or a grep pattern does not trigger anything; only segments that actually start with git or cargo are inspected. Leading VAR=value environment assignments and global options (cargo +nightly test, git -C path ...) are parsed past.

Rules

  • Validation commands need a pi timeout value in seconds. Values from 1500 seconds upward are rejected.
  • Validation commands cannot run in parallel within one agent turn, whatever ecosystem they come from.
  • A cargo workspace-wide test run needs one-time approval: cargo test --workspace, --all, or — in a virtual workspace — plain cargo test. In a single-crate repository plain cargo test is the natural command and is allowed. To test one crate in a workspace, use cargo test -p <crate>.
  • A high-risk Git operation cannot be combined with another one in the same bash call.
  • A Git operation that discards uncommitted worktree/index state needs one-time user approval. git checkout -- <pathspec>, git checkout ., git checkout -f, git restore (worktree forms), git reset --hard/--merge, git clean without --dry-run, git stash pop/apply/drop, and git switch --discard-changes hit a confirmation dialog on the first run. A plain git checkout <branch> without force is allowed without a dialog: git carries uncommitted changes over or refuses, and the branch form is textually identical to the file form, so a dialog on every checkout would be constant noise. The command is still recorded in the repeat detector. Without a dialog-capable UI, the call is blocked and the agent is told to ask for /guard allow-discard.
  • A recursive rm (-r, -R, --recursive, including bundled forms like -rf) is blocked when its target is unrecoverable: the filesystem root (/, /*), a system directory (/usr, /etc, /var, /opt, /System, /Library, /Applications, /private, /bin, /sbin, /boot, /dev, /proc, /sys) or its immediate /* contents, the home directory (~, ~/*, $HOME), the whole current or parent directory (., .., ../*), a bare *, or a bare unexpanded variable (rm -rf $DIR — empty expansion promotes the next path segment to root). Scoped targets are fine: rm -rf build, rm -rf ./dist, rm -rf *.o, deep paths like /var/folders/…. git rm is not affected.
  • The same high-risk Git operation is blocked when repeated without an intervening successful state change: an edit or write tool call, a file-mutating bash command (redirects, rm, mv, sed -i, git checkout/pull/reset/stash/apply/am, plain cargo fmt), or /guard reset. One immediate re-run is allowed.
  • The same validation command is blocked after two runs without an intervening state change (flaky-test confirmation is expected to be quick).

The guard recognizes cargo test, cargo nextest run, cargo check, cargo clippy, and cargo fmt --check as cargo validation commands, plus the JavaScript/TypeScript and Python runners listed above. High-risk Git operations are git stash pop/apply/drop, git reset --hard/--merge, worktree-discard forms of git restore, git checkout of anything that is not a provable branch creation (-b, -B, --orphan) — plain git checkout <branch> and git checkout <file> are textually indistinguishable and both go through the repeat detector, while provable discard forms (--, force, an argument that exists on disk) additionally hit the first-run discard dialog — plus git clean without --dry-run, git rebase, git merge, git cherry-pick, and git revert. The --abort/--quit forms are allowed, since they are how you escape a conflicted rebase or merge. Recursive rm rules are listed above.

Segment splitting is quote-aware: a ;, |, or && inside a commit message or grep pattern does not split anything, and each pipeline stage is classified separately, so echo x | pytest still classifies the pytest stage. The classifier still never inspects inside scripts, aliases, or command substitutions.

Blocked calls also send a steering message into the agent context, so the model receives the reason and can choose another action. The guard does not terminate the agent run.

Limits to be aware of: the classifier is a heuristic, not a shell parser. Quoted strings and pipelines are handled, but arbitrary nesting (bash -c '...', $(...)) is not classified, so this remains a workflow deterrent rather than a security boundary. It should be paired with pi's sandbox and permission mechanisms where real protection is needed.

Install

Install the package globally:

pi install git:github.com/vi2q/pi-execution-guard

To install it only for the current project:

pi install -l git:github.com/vi2q/pi-execution-guard

Review the source before installing it. Pi extensions run with the same host permissions as the agent.

Commands

Use these commands from the pi prompt:

/guard allow-workspace
/guard allow-discard
/guard reset

allow-workspace permits the next workspace-wide test. allow-discard permits the next worktree-discard Git operation without a dialog (the escape hatch for headless runs). reset clears the repeat detector after a deliberate state change outside pi's tools.

Development

Try the extension without installing it:

pi -e ./extensions/command-guard.ts

About

Execution guard for pi bash commands

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages