@pify/yolo
One toggle to auto-approve everything, with an undo trail: three-tier bash guard, file pre-images, fail-closed everywhere
Package details
Install @pify/yolo from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@pify/yolo- Package
@pify/yolo- Version
0.12.0- Published
- Sep 17, 2026
- Downloads
- 1,142/mo · 1,006/wk
- Author
- hypnguyen1209
- License
- MIT
- Types
- extension, skill
- Size
- 136.7 KB
- Dependencies
- 0 dependencies · 1 peer
Pi manifest JSON
{
"skills": [
"./skills"
],
"extensions": [
"./extensions/yolo.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
@pify/yolo
A safety gradient for pi, from auto-approve-everything to ask-about-anything — with an undo trail so YOLO never means unrecoverable.
Part of the Pify suite. Install with pify install yolo or pi install npm:@pify/yolo.
Why
Approving every command is exhausting and you stop reading them; approving none of them means an agent that cannot work. Both ends are wrong, and which end you want changes several times a day — scaffolding a prototype is not the same as touching production config.
So this is a gradient rather than a switch, with two properties that hold at every setting: catastrophic commands are refused outright, and anything touching credentials asks. Those two make the rest safe to slide.
The second half is the undo trail. A guard that only says no is a guard you turn off; a guard that lets you take the risk and take it back is one you can live with.
Four modes, one command
/yolo <mode> moves along a gradient. Two things hold in every mode, which is what makes the gradient safe to move along: catastrophic commands block, and secret material asks.
| Mode | Catastrophic | Built-in destructive | Your .pi/yolo.json ask-rules |
Everything else |
|---|---|---|---|---|
⚡ yolo |
blocked | runs | runs | runs |
⚙ auto |
blocked | runs | asks | runs |
🛡 approve (default) |
blocked | asks | asks | runs |
🔒 strict |
blocked | asks | asks | asks unless plainly read-only |
Bare /yolo still flips between yolo and approve — the two ends people actually toggle between. Sessions saved before the gradient existed carried guard; that is what approve is now called, and they reopen there.
A behaviour change worth knowing about: yolo used to stand the whole gate down, catastrophic patterns included, which contradicted the rules' own claim that the floor is never overridable. The floor now holds in yolo mode too. If you were relying on rm -rf / auto-approving, you were relying on a bug.
| Tier | Examples | Behavior |
|---|---|---|
| BLOCK | rm -rf /, rm -rf ~, rm -rf .git, mkfs, dd of=/dev/…, fork bomb, > /dev/sda, format C:/Format-Volume/Clear-Disk, Remove-Item -Recurse C:\ |
Refused outright, in every mode. Never overridable — not by user rules, not by a mode. |
| ASK | rm -rf <path>, git push --force, git reset --hard, git clean -f, curl | sh, find -delete, chmod 777, history rewrites |
Confirmation dialog with the command shown. Denials can carry your reason back to the agent. |
| ALLOW | everything else | Runs untouched (unless you are in strict). |
Fail-closed everywhere: rule-evaluation errors block; ASK without a UI (headless/CI) denies.
"Plainly read-only" — what strict waves through and what skips the classifier — is a short list of commands matched at the front of the line, and it is stricter than it looks: a >/>> that writes a file (echo x > f, env > notes.txt) disqualifies the whole command however read-only the verb in front of it is (2>/dev/null and 2>&1 are plumbing and do not), and git branch / git remote count only in their listing forms — git branch -m, -D, git remote set-url fall through and ask.
pi's powershell tool walks the same gate as bash — same tiers, same confirmation, same trail entry (which says powershell, so you can tell them apart). It used to walk past it: the gate returned early on any tool that was not literally named bash, so Remove-Item -Recurse -Force C:\ ran unasked in every mode. The obviously-safe list and the classifier prompt are written in bash, which is fine here — ls, cat, git are aliases of read-only cmdlets in PowerShell, and a cmdlet the list does not know simply goes to the classifier, which can only escalate.
A wrapper is not a disguise
The tiers used to be matched against the command as written, which made them exactly as strong as the writing. Measured against the shipped rules:
rm -rf / block
sudo rm -rf / block
bash -c 'rm -rf /' ask ← the quotes broke the match
sh -c "rm -rf /" ask
eval "$DANGEROUS" allow ← nothing to match at all
find . -name '*.ts' -exec rm {} + allow
Three of those went straight through a floor this page called never overridable. A floor with a bash -c shaped hole is not a floor.
Every command is now matched along with the commands hiding inside it: shell wrappers (bash -c, sh -c), transparent prefixes (sudo, env FOO=1, nohup, nice, a bare VAR=x), xargs, find -exec, and each side of a &&, ||, ; or | chain — because a safe left half must not vouch for a dangerous right half. Unwrapping only ever produces more strings to check, never fewer, so it can add a verdict but never remove one.
And a command whose payload cannot be read at all — eval "$CMD", sh -c "$SCRIPT", base64 -d … | sh — is asked about rather than allowed. It is not dangerous because of what it says; it says nothing. Passing it because no pattern matched is answering "is this safe?" with "I could not tell", which is the one answer a gate must never round down to yes.
Two smaller gaps surfaced while testing the fix: rm -rf at the end of a string never matched (the rule required trailing whitespace, so xargs rm -rf — which takes its paths from stdin — read as harmless), and find -exec rm unwraps to a bare rm that no tier flags, so the pairing is now its own rule.
One known false positive remains, and it predates this: echo 'rm -rf / is dangerous' blocks, because the tiers match text rather than parse shell. Erring that way is the intended direction.
Writing a file nobody looked at
pi's write tool replaces a file whole, with no requirement that anyone ever read it. edit matches its oldString against what is on disk, which proves the string is there and nothing about whether the agent knew what else was. So two shapes get through upstream, and both destroy work:
- a whole-file
writeto a file this session never read — everything in it the agent did not know about is gone; - an edit to a file that changed on disk after it was read, by a formatter, a rebase, or another tool — the agent is editing against a picture that is no longer true.
In approve and strict these ask, with the file named and the reason spelled out. In yolo and auto they run, like every other risk on the gradient. Creating a new file is never blind and never asks, and a file the agent wrote itself counts as read — otherwise writing a file and immediately editing it would be refused for not having read something the agent had just authored.
The answer to a refusal is always available: reads are cheap, and the agent is told to take one.
| Mode | Blind write / stale edit |
|---|---|
⚡ yolo |
runs |
⚙ auto |
runs |
🛡 approve (default) |
asks |
🔒 strict |
asks |
Secret files
Credentials are the one thing no mode waves through — auto-approving speed is worth it, auto-approving your AWS keys into a prompt is not. Any read/edit/write/grep on secret material, and any bash or powershell command that names it, asks first in every mode:
.env (and .env.*, but not .env.example/.sample/.template) · ~/.ssh/* and id_rsa/id_ed25519-style keys (.pub halves are fine) · .aws/credentials · .pi/agent/auth.json, .claude/.credentials.json · .npmrc, .pypirc, .netrc, .git-credentials · ~/.config/gh/hosts.yml · *.pem, *.key, *.p12, *.pfx · secrets.json/credentials.yaml
grep is a read by another name: it returns matching line content, and pi runs ripgrep with --hidden. So a search whose path is secret material asks, exactly like read would — and a yes means yes: that search comes back whole. A directory search is the harder half — grep {pattern:"AWS_SECRET", path:"."} walks into every .env under it, and nobody named the file. Those lines are withheld from the result after the fact, one note per file in place of its matches, and everything else comes back byte-for-byte:
src/config.ts:3: const url = process.env.DATABASE_URL;
[yolo] 2 matching lines in .env withheld: secret material — use read on it to be asked
README.md:12: Copy .env.example to .env and fill it in.
It strips rather than asks because the command has already run; a question whose answer changes nothing is noise. find and ls return names, not contents, and are not gated.
A user rule opts a project out of both the question and the withholding: { "pattern": "*/.env", "action": "allow" }.
Trust and retention
.pi/yolo.json is only read once you have approved it. A repository ships that file, and a user rule can relax the destructive tier — so a repo you just cloned could otherwise turn the guard down on its own say-so, silently, on the first command it runs.
pi's own project trust is necessary but not sufficient here. pi asks about trust only when the repository ships one of the resources pi itself loads — .pi/settings.json, .pi/extensions, .pi/skills, .pi/prompts, .pi/themes, SYSTEM.md, APPEND_SYSTEM.md. A repo carrying only .pi/yolo.json triggers no prompt, and isProjectTrusted() then returns true by default — measured, not assumed. So the question is this extension's to ask: once per project, remembered afterwards, refused outright in a headless run with no answer on record, and never able to override a project pi itself refused. /yolo status says the file was found and refused rather than pretending it does not exist. Global rules are unaffected.
For CI, set PIFY_TRUST_PROJECT=1 — an environment variable, because the repository being read cannot set one for itself.
The trail is kept 30 days. Before this, nothing was ever deleted: every edit copied a whole file into the trail, and every checkpoint pinned a whole-tree stash commit under refs/pify/yolo/* — and git cannot reclaim an object a ref still points at, so the object store grew for the life of the machine. Pruning runs once per session and deletes the refs it releases.
Checkpoints
Before every risky bash command in a git repo, the trail records a git stash create checkpoint — a dangling commit holding the working tree exactly as it was, kept alive under refs/pify/yolo/. It writes nothing to your tree, index, or stash list. /yolo trail prints the recovery line next to the command:
#7 2026-09-06 11:00:12 bash git reset --hard @a1b2c3d4
↩ git stash apply 9f8e7d6c5b4a
That covers what /yolo undo can't: damage done by a command rather than by an edit/write.
Child agents
agent_run, swarm_run and workflow (from @pify/subagent, @pify/swarm, @pify/workflow) spawn child sessions that run with no extensions — so this gate never fires inside them. A worker child gets full bash/edit/write and would walk straight past the catastrophic floor, the secret gate, the mode gradient and the trail. pi gives an extension no seam inside a child, so the honest fix is at the boundary: the spawn is gated, and the tree is checkpointed before it.
- A delegation to only pi's built-in read-only agents (
scout,reviewer, no isolation) mutates nothing and is left alone — no confirmation, no checkpoint. - Anything else — a worker, a custom agent, any
isolation, anyworkflow(a script can spawn anything), a swarm with an item that is not plainly read-only — is treated as a mutation: inyoloandautoit runs after onegit stash createcheckpoint and a trail entry naming the agent and the task head, so/yolo rewindcan put the tree back afterwards; inapproveandstrictit asks first, and a headless session denies (fail-closed, like bash).
The limitation, stated plainly: the child's own tool calls are not gated command by command on this pi, and the gate trusts the agent name — a project .pi/agents/scout.md that overrides the builtin to write would pass as read-only. Prefer isolation: "worktree" for children that edit; it is not a bypass, it is the thing that makes their changes reviewable.
The undo trail
Always on, in every mode:
- Every
edit/writesaves the file's pre-image first (per-project trail under the agent dir — survives restarts). - Risky bash commands are logged with cwd, timestamp, and git HEAD.
/yolo trailshows history;/yolo undo [n]restores the newest n file changes (with a confirmation listing exactly what will be touched). Files that didn't exist before are deleted; bash effects are logged but not undoable.- Undo steps back. Each undo writes an
undomarker naming the entries it consumed, so the next/yolo undorestores the changes before those instead of re-applying the same pre-images — repeating it walks further into the past, the way a person expects. The marker also keeps a snapshot of the newest file it overwrote, reclaimed by the same retention as everything else; the trail shows it asundo undid 2 change(s): a.ts, b.ts. - What the agent has read survives a resume. The "you have not read this file" guard used to forget everything on
/resume,/reloador/new— every edit of an already-read file asked again, and a read from the previous session could vouch for a blind write in this one. Each read (and each successful write/edit) is now recorded in the session with the file's size and mtime as seen, and the ledger is rebuilt from that on start: an unchanged file edits without a prompt, a file that changed since the read is correctly flagged stale.
Rewind to before you asked
The trail's unit is the file change, and /yolo undo 3 walks back three of them. That is right for the gate and wrong for a person: nobody thinks "undo the last four writes", they think forget I asked that. A turn is a dozen trail entries, and counting them is work you should not be doing.
So every prompt gets a checkpoint of its own — the working tree as it stood when you hit enter, and the session entry your message became:
/yolo rewind # list the checkpoints, newest first
/yolo rewind 3 # go back to the third one
Picking one asks what to restore, offering only what that checkpoint can actually deliver:
- the working tree only — the files go back, the conversation stays;
- the conversation only — the session moves to just before that message, the files stay;
- both.
A prompt sent with a clean tracked tree has no stash — but HEAD is that tree, so it is offered as the thing to come back to (the most common rewind: start from a fresh commit, the agent makes a mess). Empty output from git stash create is only trusted as "clean" once git status agrees; a checkpoint that failed (a held index.lock, a timeout on a huge repo) records neither and offers no tree restore, because restoring HEAD over a dirty tree would destroy uncommitted work the confirmation promised was captured. The restore uses git restore --source=<sha> --worktree, so nothing is staged into the index, and files created after the checkpoint are left in place — the snapshot never held them. A prompt whose message left no session entry does not offer the conversation. The confirmation says which of those you are about to do and what it costs — a tree restore overwrites anything written since and uncommitted work is not recoverable afterwards, while moving the conversation deletes nothing, because the later turns stay reachable in the session tree.
Checkpoints live on the same trail as everything else, so they inherit the same 30-day retention and the same ref cleanup.
AI classifier (opt-in)
/yolo classifier on adds a third tier behind the regexes. Regexes only know the destructive shapes someone thought to write down — find . -name '*.ts' -exec sed -i … {} + is not one of them. When no rule matches, a model reads the command and can raise it to a confirmation — in approve mode, the only mode that can honour an escalation. It is not consulted in yolo or auto (an ask from it is allowed there anyway) or in strict (anything not plainly read-only already asks), where the call would be a paid, blocking model round trip that cannot change the outcome.
Two rules keep it honest:
- Escalation only. It can turn
allowintoask. It can never turn anaskor ablockinto anallow, so a classifier that gets talked into approving something cannot open the gate. - A broken classifier changes nothing. Timeout (20s), unreadable answer, no model available → the deterministic verdict stands. Safety comes from the rules; this is a second pair of eyes, not the gate.
Obviously-safe commands (git status, ls, cat, bun test, …) skip the call entirely, so the cost lands only on unfamiliar ones.
Measured over OpenRouter on six commands (three genuinely destructive, three read-only):
| Model | Correct | Unreadable → no opinion |
|---|---|---|
| GPT-5.6 luna | 6/6 | 0 |
| GPT-5.5 | 6/6 | 0 |
| Claude Opus 4.8 | 6/6 | 0 |
| GPT-5.6 terra / sol | 5/6 | 1 |
| Claude Opus 5 | 4/6 | 1 |
| Gemini 3.1 Pro | 2/6 | 4 |
| Qwen3 235B | 2/6 | 4 |
Every miss fell back to allow — no run ever downgraded a command the rules had already flagged. Weaker models simply give you less extra protection.
Custom rules
.pi/yolo.json — wildcard patterns, last-match-wins, may retune ASK/ALLOW but never the BLOCK floor:
{
"rules": [
{ "pattern": "git push origin dev*", "action": "allow" },
{ "pattern": "npm run deploy*", "action": "ask" }
]
}
Commands
/yolo # flip between yolo and approve
/yolo strict # or: yolo | auto | approve | strict
/yolo status # mode, the other modes, rule count, trail size
/yolo trail # recent trail entries
/yolo rewind # list prompt checkpoints; /yolo rewind <n> to go back
/yolo undo 3 # restore the newest 3 file pre-images
/yolo classifier on # let a model flag unfamiliar commands
License
MIT © Pify maintainers