An AI assistant that can run commands will run the command it believes is correct, at the speed of a command. The difference between a helpful session and an expensive one is whether the action it took can be taken back.
The idea
Sort every action by whether you can undo it, and give supervision to the ones you cannot. A code edit is reversible if you committed before you started, because git holds only the versions you saved. Work you never committed has nothing you can rely on behind it, and mid-session that is most of the tree, which is why commit before you delegate is what puts code edits in the safe column. A deleted production table is not reversible, unless a backup exists and you have restored from it before. Anything that leaves your machine and reaches other people, such as an email send or a payment capture, cannot be recalled at all. The useful test is reversibility: it tells you which actions to sit in front of. Plenty of dangerous-sounding commands are fine to let run, and a few dull ones are not.
reversibility examples supervision
free to undo edit a file, run tests let it run
(if you committed read logs
first)
undoable with effort force-push, drop a local table read first,
(backup, reflog, revert a deploy then approve
redeploy) install a package (its setup
scripts run on your machine,
outside the diff you review)
cannot be undone email to real users human types it
delete production rows
a migration on live data
revoke a key other systems useHow it works
Five categories account for nearly all of the real damage.
- Deleting data.
rm -rf,DROP TABLE,DELETEwithout aWHEREclause, emptying a storage bucket. The command succeeds silently: no confirmation step, no recycle bin. - Touching production directly. A live server, the production database, the DNS records for your domain. A change here has an audience immediately.
- Credentials. Reading, printing, or rotating a key. An assistant that reads
.envto debug a connection has put your secrets into the session transcript, and possibly the vendor's logs. - Migrations on live data. A A file that changes the shape of the database, such as adding a column or renaming a table. It runs against real rows, so a wrong one can lose them. that drops a column removes the data in it. Running it against production is a one-way action even when the file itself is in git.
- Sending email to real users. Nothing recalls a broadcast. A test that reaches the live list instead of a seeded one is the usual way this happens.
git push --force sits at the edge. It is recoverable through the
A local log git keeps of where your branch pointed recently, so you can move it
back after a mistake. It exists only on the machine that did the work. on a machine that
still has the old commits, and much harder if the only copy of that branch was the remote one.
There is a sixth case the reversibility test does not cover, because it assumes you chose the
action. Your assistant reads whatever you point it at: a GitHub issue, a
dependency's README, a web page, a row a stranger typed into your own app. Text in any of those can
be phrased as an instruction, and an assistant working through it may act on that text rather than
report it. You cannot review a decision you did not know was being made, which is why the permission
list matters more than the supervision. An assistant that cannot delete, cannot reach production and
cannot read .env is a far smaller problem on the day it reads something hostile.
What to do
- Replace blanket approval with a written rule list. In Claude Code,
/permissionsopens the allow and deny lists, and each entry is a tool name with an optional pattern:Bash(git status:*)andRead(src/**)on the allow side,Bash(rm:*)andRead(./.env*)on the deny side. Note the*:Read(./.env)is an exact path and leaves.env.localreadable. Deny rules are pattern matches, so they stop the accident rather than a determined bypass. - Give it a database that is not production. Make a second Supabase project, apply your
migrations to it, and put that connection string in
.env.local. The credential in your project folder, where the assistant works, is always the development one. Production credentials live in your host's environment settings. If you ever genuinely need one locally, put it in a file the deny rule covers rather than typing it into the terminal, where it lands in your shell history and in the session transcript. - Ask for the command instead of the execution when a destructive step is genuinely needed: "Write the migration and the rollback, and show me both. Do not run either." You paste it yourself, against the development database first.
- Take a backup before the irreversible step, and prove it restores by loading it into a separate empty database, never over the live one. An untested backup is a belief. Backups covers how to make one.
Where it breaks
Supervision only works if you read what you approve. A prompt that asks for confirmation twenty times an hour trains you to press yes, which is how a wrong command gets approved by someone who was technically supervising. Narrow the allowlist until the prompts are rare enough to read. The other limit is scope: an assistant with a legitimate database connection can issue a destructive statement without running a shell command at all, so a shell deny list does not protect the data. What closes that gap is the connection string. Give the app a database user that can read and write rows but cannot drop tables, and keep the owner credentials for migrations you run by hand.