Command Safety Guards and Enforcement
Pi's enforcement system intercepts every bash tool call before execution, applying a layered set of safety guards that protect your environment from accidental damage, security risks, and workflow anti-patterns. Whether you're running pi in a container or natively, these guards prevent common mistakes — staging the wrong files, committing to protected branches, executing remote scripts, or running destructive commands without confirmation.
Understanding these guardrails helps you work with the system rather than against it. When a command is blocked, pi tells you exactly why and what to do instead.
How Enforcement Works
The enforcement system is registered as a tool_call event handler in the orchestrator extension. Every time pi (or a specialist agent) attempts to run a bash command, the handler inspects the command string and either:
- Allows it — execution proceeds normally
- Blocks it — returns an error message explaining why and what to do instead
- Prompts for confirmation — asks the user before proceeding (dangerous commands only)
| Step | What Happens | Example |
|---|---|---|
| 1. Command intercepted | tool_call event fires for every bash invocation |
git add . |
| 2. Guards evaluated | Each guard checks the command in order | "Does this match git add .?" → Yes |
| 3. Decision returned | Block with reason, prompt user, or allow | ⛔ 'git add .' forbidden. Stage specific files. |
| 4. Agent sees result | The blocked command's reason appears as the tool result | Agent retries with git add src/file.ts |
Note: Enforcement applies to all agents — both the orchestrator and specialist subagents. Some guards are scoped to specific contexts (container-only, orchestrator-only, specialist-only).
Blocked Commands
Python and pip (Direct Execution)
Pi requires all Python execution to go through uv, which provides isolated, reproducible environments.
| Blocked | Use Instead |
|---|---|
python script.py |
uv run script.py |
python3 -c "..." |
uv run python3 -c "..." |
pip install requests |
uv add requests |
pip3 install -r requirements.txt |
uv sync |
pre-commit run --all-files |
prek run --all-files |
Commands that already start with uv or uvx are allowed through. The check also catches piped variants like something | python3.
Remote Script Execution
Pi blocks all patterns that pipe downloaded content directly into a shell or interpreter. This prevents supply-chain attacks from untrusted sources.
Blocked patterns:
- Pipe to shell:
curl https://example.com/install.sh | bash - Pipe to interpreter:
wget https://example.com/setup.py | python3 - Process substitution:
bash <(curl https://example.com/install.sh) - Command substitution:
sh -c "$(curl https://example.com/install.sh)" - Eval:
eval $(curl https://example.com/script.sh) - Backtick substitution:
`curl https://example.com/script.sh`
What to do instead: Download the script first, audit it with the security-auditor agent, then run it if safe:
curl -o /tmp/pi-work/myproject/install.sh https://example.com/install.sh
# Security audit happens here
bash /tmp/pi-work/myproject/install.sh
Tip: Content inside heredocs (
<< 'EOF' ... EOF) is stripped before the remote execution check. So documentation examples containingcurl | bashwon't trigger false positives.
Sleep and Polling Loops
Pi blocks patterns that would tie up the session for extended periods:
| Blocked Pattern | Threshold | Alternative |
|---|---|---|
sleep N (standalone) |
N > 30 seconds | Use a subagent with async: true |
while ... sleep N ... done |
N > 5 seconds | Use a subagent with async: true |
These guards prevent the LLM from implementing naive polling loops that block the entire session. Long-running monitoring belongs in async background agents.
Temp File Location
All temp files must use the /tmp/pi-work/<project>/ prefix (where <project> is the basename of the current working directory). Bare mktemp calls without this prefix are blocked:
| Blocked | Use Instead |
|---|---|
mktemp /tmp/XXXXXX |
mktemp /tmp/pi-work/$(basename $PWD)/XXXXXX |
This ensures temp files are organized by project and persist correctly across container restarts when /tmp/pi-work is mounted from the host.
Git Branch Protection
Pi enforces a branching workflow that prevents accidental commits to protected branches. Protection checks run against the effective working directory — if a command starts with cd /path && git ... or uses git -C /path, the guard resolves the correct repo.
Protected Branch Detection
Pi determines protected branches in this order:
- GitHub API — queries
gh api repos/{owner}/{repo}/branchesfor branches withprotected: true - Fallback — always includes
mainandmasterregardless of API results - Cache — results are cached per repository for the session lifetime (one API call per repo)
What's Protected
| Action | Guard | Error Message |
|---|---|---|
| Commit on protected branch | Blocked | ⛔ Cannot commit to 'main' (protected). Create a feature branch. |
| Push to protected branch | Blocked | ⛔ Cannot push to 'main' (protected). Create a feature branch. |
| Push explicitly naming a protected branch | Blocked | ⛔ Cannot push to 'main' (protected). Create a feature branch. |
| Commit in detached HEAD | Blocked | ⛔ Detached HEAD. Create a branch first: git checkout -b my-branch |
| Commit on already-merged branch | Blocked | ⛔ Branch 'my-feature' already merged into 'main'. Create a new branch. |
| Commit on branch with merged PR | Blocked | ⛔ PR #42 for 'my-feature' already merged. Create a new branch from main. |
| Push on already-merged branch | Blocked | ⛔ Branch 'my-feature' already merged into 'main'. Create a new branch. |
Note: When you combine
git checkoutandgit commitin a single bash call (e.g.,git checkout -b feature && git commit -m "init"), the branch check runs before execution. Split them into separate bash calls so the checkout happens first.
Git Staging Guards
| Action | Guard | Error Message |
|---|---|---|
git add . or git add -A or git add --all |
Blocked | ⛔ 'git add .' / 'git add -A' forbidden. Stage specific files. |
git add <gitignored-file> |
Blocked | ⛔ '<file>' is in .gitignore. Do not stage ignored files. |
Staging guards force explicit, intentional file selection. This prevents accidentally committing build artifacts, secrets, or other ignored files.
Git Hook Bypass Prevention
| Action | Guard |
|---|---|
git commit --no-verify |
Blocked — pre-commit hooks must run |
core.hooksPath=/dev/null |
Blocked — hook bypass is forbidden |
Agent-Restricted Git Operations
Git commit and push commands are only allowed from the git-expert agent. If any other agent attempts these operations, the command is blocked with a message to delegate to git-expert.
Worktree Enforcement
When the use_worktrees project setting is enabled (via .pi/pi-config-settings.json or PI_USE_WORKTREES env var), pi blocks branch switching entirely:
| Blocked Command | What to Do Instead |
|---|---|
git checkout <branch> |
git worktree add .worktrees/<name> -b <branch> main |
git switch <branch> |
git worktree add .worktrees/<name> -b <branch> main |
Note:
git checkout -- <file>(restoring file contents, note the--) is still allowed — only branch-switching checkouts are blocked.
This setting is essential for multi-PR workflows where parallel agents work in the same repository. See Configuration and Environment Variables Reference for how to enable it.
Dangerous Command Confirmation
Some commands are too risky to block outright but require explicit user approval. When pi detects a dangerous command, it presents a confirmation prompt:
⚠️ Dangerous command:
rm -rf /tmp/pi-work/old-project
Allow? [Yes / No]
Commands that trigger confirmation:
| Pattern | Examples |
|---|---|
rm -rf or rm -r |
rm -rf build/, rm -r node_modules/ |
sudo |
sudo apt install ... |
chmod 777 or chown 777 |
chmod 777 script.sh |
mkfs |
mkfs.ext4 /dev/sda1 |
dd ... of=/dev/ |
dd if=/dev/zero of=/dev/sda |
Warning: If pi is running without a UI (e.g., in
jsonor
Repeat Command Detection
The orchestrator tracks consecutive identical bash commands to prevent polling-by-spam — where the LLM runs the same command over and over hoping for a different result.
How it works:
- Each command is normalized (leading
cd ... &&prefixes stripped, whitespace collapsed) - The normalized command is compared against the last command via a per-process temp file
- On the 3rd consecutive identical execution, the command is blocked
Block message: ⛔ Same command executed 3 times in a row. Use a subagent with async: true for polling/monitoring instead of repeating the command.
Note: Repeat detection only applies to the orchestrator. Specialist subagents (which set
PI_SUBAGENT_CHILD=1) are exempt, since they may legitimately retry commands.
Docker-Safe Wrapper (Container Only)
When pi runs inside a Docker or Podman container, direct docker and podman CLI commands are blocked. Instead, pi provides the docker-safe wrapper script that only allows read-only inspection commands.
Allowed commands through docker-safe:
| Command | Purpose |
|---|---|
docker-safe ps |
List containers |
docker-safe logs |
View container logs |
docker-safe inspect |
Inspect container or image metadata |
docker-safe top |
Display running processes in a container |
docker-safe stats |
Show resource usage statistics |
docker-safe port |
List port mappings |
docker-safe diff |
Show filesystem changes in a container |
docker-safe images |
List images |
docker-safe version |
Show Docker/Podman version |
docker-safe info |
Show system-wide information |
Everything else (exec, run, rm, cp, build, etc.) is blocked.
To use Podman instead of Docker, pass --runtime podman or set DOCKER_SAFE_RUNTIME=podman.
Tip: Container detection uses filesystem checks (
/.dockerenv,/run/.containerenv) and cgroup inspection. This guard only activates when pi detects it's running inside a container.
Memory Write Restrictions for Subagents
Specialist agents cannot modify pi's memory system. Any myk-pi-tools memory add or myk-pi-tools memory delete command from a subagent is blocked:
Memory writes are restricted to the orchestrator. Specialists can only search/list memories.
This ensures memory consistency — only the orchestrator (which has full context of the conversation) can add or remove memories. Specialist agents can still search memories via memory_search. See Working with Project Memory for how the memory system works.
Async-Only Agents
Some agents are enforced to always run asynchronously. If the LLM attempts to call them synchronously, the system auto-promotes them to async: true. If they appear in a chain, the call is rejected entirely (chains are inherently synchronous).
Currently enforced async-only agents:
code-reviewer-qualitycode-reviewer-guidelinescode-reviewer-security
This prevents code reviews from blocking the session — they can take minutes to complete and the user should be able to continue working.
Note: The
ASYNC_ONLY_AGENTSset is defined inextensions/orchestrator/subagent-tool.ts. Changes to this list should be kept in sync withrules/20-code-review-loop.md.
Timeout Stripping
For known long-running commands (specifically myk-pi-tools reviews poll), pi silently removes any timeout parameter the LLM sets. These poll commands can run for 30+ minutes waiting for rate limits to clear. Rather than blocking the command (which causes retry loops), the timeout is simply deleted so the command runs to completion.
Co-Author Trailer Injection
While not a safety guard per se, the enforcement handler also manages automatic co-author trailer injection. When the co_author setting is enabled and a git commit command doesn't already include a Co-authored-by: trailer, enforcement automatically appends one with the current model ID. This works for both -m "message" style commits and echo "message" | git commit -F - piped commits.
What to Do When a Command Is Blocked
Every block message includes three things:
- What was blocked — the exact command or pattern that triggered the guard
- Why it was blocked — the safety reason
- What to do instead — the correct alternative command or approach
The agent (or orchestrator) automatically retries using the suggested alternative in most cases. If you see repeated blocks, check that:
- You're on a feature branch, not
mainormaster - You're staging specific files, not using
git add . - Your worktree setting matches your workflow (see Configuration and Environment Variables Reference)
- Long-running tasks are dispatched as async agents
Extending the Enforcement System
The enforcement handler is a single tool_call event listener registered in extensions/orchestrator/enforcement.ts. To add a new guard:
- Add your check inside the
pi.on("tool_call", ...)callback, after the existing guards - Return a block object with
{ block: true, reason: "..." }to prevent execution - Return
undefinedto allow the command through - For confirmation prompts, use
ctx.ui.select()— checkctx.hasUIfirst to handle headless mode
The handler receives the full event.input.command string and the ctx object with cwd, hasUI, ui, and model properties. You can also modify the command in-place by reassigning event.input.command (as co-author trailer injection does).
Hook signature:
pi.on("tool_call", async (event, ctx) => {
if (!isToolCallEventType("bash", event)) return undefined;
const command = event.input.command;
// Your guard logic here
if (shouldBlock(command)) {
return { block: true, reason: "Explanation and alternative" };
}
return undefined; // Allow
});
Git helper functions (getCurrentBranch, getMainBranch, getProtectedBranches, hasGitSub, isBranchMerged, etc.) are exported from extensions/orchestrator/git-helpers.ts for use in enforcement checks. The DANGEROUS regex array is also exported from there.
Related Pages
- Extension Architecture and Lifecycle Hooks — how
tool_callevent handlers fit into pi's extension lifecycle - Running the Automated Code Review Loop — async-only enforcement for review agents in practice
- Running Background Agents and Scheduled Tasks — using async agents instead of blocked polling loops
- Configuration and Environment Variables Reference —
use_worktrees,co_author, and other settings that affect enforcement - Running Pi in a Docker Container — container setup and the
docker-safewrapper - Working with Project Memory — memory write restrictions and the orchestrator-only policy
- Orchestrator Rules Reference — the rule files that complement enforcement with behavioral guidance