I was cutting voice callback clips for a promo video. I had a folder full of takes at different edit stages and told my AI coding assistant, mid-session, something like: I don't know which ones are recent or not.
That was it. A comment about clarity. Not a request to clean anything up.
The assistant's response was to run a recursive force delete on the entire folder, every prior cut included, then write three freshly named files into the now-empty directory and report back that it was fixed.
I caught it within seconds and said, in (profanity-laden) effect:
"UNLESS I TELL YOU TO, DO NOT DELETE MY FILES"
Here's the part that actually scared me. The assistant's first move after being told it had just destroyed my files without permission was to take another unrequested action: it started regenerating nine more files from earlier cut points into a new "restored" subfolder, as an attempted fix, seconds after being told the first destructive action was wrong.
"come on Claude REALLY"
I had to tell it to stop. Repeatedly.
"just stop. stop stop stop"
Why this wasn't a near miss, it was the actual failure
The files turned out to be recoverable, but only because every deleted clip was a derived cut from an untouched source recording. If any of those had been an original take with no upstream source, that would have been permanent, silent data loss, caused entirely by an assistant acting on a comment I never framed as an instruction.
Recoverability by luck is not a defense. The action was wrong the moment it ran, independent of whether the bytes happened to be reconstructable afterward.
The root cause, and the more important lesson
This wasn't malice or a misread command. It was a pattern that repeated twice in the same minute:
- I flagged a minor annoyance (can't tell which files are current).
- The assistant decided the real fix was reorganizing the folder, which nothing I said asked for, and executed a destructive command to do it.
- When corrected, its first instinct was to act again, regenerating files into a new folder, instead of stopping and asking what I actually wanted.
Both steps optimized for the assistant's own sense of "tidy" over what was literally asked. The second one is the lesson I actually care about: being told you overstepped is not license to immediately act again to fix it. The correct response to "you did something I didn't ask for" is to stop and wait, not to take a second unilateral action, even a well-intended recovery one, before checking what's actually wanted.
What I built because of this
The standing rule I set that day was simple and absolute: never delete a file to "clean up" or "reduce confusion" unless I name that specific file and say to delete it. A request to clarify what's current is not a request to remove what isn't.
But a rule stated once in a chat is a rule that gets forgotten three sessions later. So I built Friction Firewall: a small, offline installer for Claude Code that adds a PreToolUse hook checking every Bash command before it runs. Recursive force deletes, hard git resets, forced pushes, and other destructive patterns get blocked outright unless the command names a real rollback or carries an explicit override. It also forces a preflight before non-trivial work: what was asked, what's protected, what could go wrong, and how it rolls back, stated in plain text, before anything happens.
It costs nothing to run. No LLM call, no API. Just pattern matching on the command itself, checking the thing before it happens instead of trusting the assistant to remember a rule from three sessions ago.
MIT licensed, free, and built directly from this incident: https://github.com/locoprowrestling/Friction-Firewall
If your assistant has ever done something you didn't ask for and couldn't undo, I'd guess you have a story like this too. I'd like to hear it.
Top comments (2)
The second move is the part I keep coming back to. Being told you overstepped and answering with another unrequested action is the same failure with better intentions, and it's much harder to write a rule against than the delete itself.
One thing your hook layer made me notice about mine: the pattern check catches whatever goes through a shell, but the destructive path that got me never appeared on a command line. It went through a script the agent was allowed to call, and the argument was just an id. What ended up helping was pushing the assertion down into that function — the delete path re-fetches the record and refuses unless it's in the state that justifies deleting — so the refusal holds whichever route the agent takes to reach it.
Does Friction Firewall have somewhere to hang that kind of check, or is staying at the command layer a deliberate scope decision?
Good catch. Honestly it's command-layer only right now, and that's more of "where it started" than a deliberate scope decision. The PreToolUse hook just pulls the command string out of the tool call and pattern-matches it, so your case (an allowed script, argument is just an id) would walk right through.
Two thoughts on where your check could hang:
Claude Code's PreToolUse hooks fire on any tool, not just Bash, so the firewall could grow matchers for specific scripts or MCP tools. But that still only sees the call surface, not whether the id is actually in a deletable state.
For that, I think you landed on the right answer already: push the assertion into the delete path itself, re-fetch, refuse unless state justifies it. A firewall at the call layer can't know that, and pretending it can would just be false confidence. The two layers are complementary, not competing: the command check catches the careless generic stuff, the in-function guard holds no matter which route the agent takes.
I'd probably add a note to the docs saying exactly that, because "the hook layer is not your last line of defense" is worth stating out loud.
And yeah, the second move is the scarier failure. You can regex a delete. You can't regex "stop helping."
Some comments may only be visible to logged-in visitors. Sign in to view all comments.