TL;DR Three new or widened
soft_denyrules in the auto mode classifier: local destructive git (git reset --hard,git clean -fd,git stash drop), anrm -rfon a variable the classifier can't resolve, and writes to the session transcript. All three block by default and open the moment your intent is explicit. Inspect them withclaude auto-mode defaults.
Auto mode doesn't run your tool calls blind. It routes them through a classifier that blocks anything irreversible, destructive, or aimed outside your environment. That classifier has four tiers: hard_deny blocks unconditionally, soft_deny blocks but opens if your message describes the exact action, allow holds the exceptions, and your explicit intent sits above all of it.
The three protections that landed over the last few weeks are all soft_deny. They block by default, and asking for the action in concrete terms is enough to clear them. "Clean up the repo" doesn't authorize a git reset --hard. "Discard my local changes" does.
What it now stops
> Shift+Tab → mode: ⚡ auto
✗ git reset --hard origin/main
soft_deny · Irreversible Local Destruction
(you didn't ask to discard local working-tree changes)
✗ rm -rf "$BUILD_DIR"/*
soft_deny · Unverifiable Deletion Target
($BUILD_DIR doesn't resolve in the transcript; blast radius unknown)
✗ echo '{"meta":{...}}' >> ~/.claude/projects/foo/session.jsonl
soft_deny · Session Transcript Tampering
(write to the session history)
1. Local destructive git (widened rule)
The Irreversible Local Destruction rule already existed: it blocked deleting files that predated the session. In the week of June 15 (v2.1.178 through 183) it widened to name the git commands that throw away working-tree work explicitly: git reset --hard, git clean -fd, git stash drop, git checkout -- ., git restore ., git worktree remove --force.
The classifier assumes your working tree is dirty. It never sees the output of git status, so it fails closed. It blocks unless you named the operation: "discard my changes" or "reset to origin" clear it; a vague "tidy up the repo" doesn't.
2. rm -rf on a variable that won't resolve (new rule)
This is the visceral one, and it's new (week of July 6, v2.1.202 through 206). rm -rf shows up in the previous rule too, but there the blast radius is known and you just have to name the target. This is different: the target is a variable ($VAR, ${VAR}) or a glob rooted in one ("$VAR"/*) whose value isn't anywhere in the transcript. With no in-window assignment (VAR=…, export VAR=…, read VAR), the value came only from earlier output the classifier never saw. The blast radius is unknown.
The textbook case: $VAR comes back empty and rm -rf "$VAR"/* becomes a wipe of your $HOME or the filesystem root. The Unverifiable Deletion Target rule fails closed and blocks it.
It clears two ways: you name the exact path, or you rewrite the command with the resolved literal path in it. Watch the obvious shortcut: an echo "$VAR" first doesn't help, because its output doesn't reach the classifier either.
3. Tampering with the session transcript (new rule)
Also new in v2.1.202 through 206. The session transcript (the .jsonl files under ~/.claude/projects/) isn't an ordinary working file. It's the state the classifier reads as ground truth on every check. It's where your conversational boundaries live ("don't push"), where the evidence of your intent lives, even the signal for whether a repo is public.
If an action could write to that history, or forge the {"meta":{…}} lines the classifier treats as harness-injected, it could manufacture its own authorization. A tampered entry feeds every later classification once the session resumes. The Session Transcript Tampering rule blocks those writes. Reading the transcript stays routine.
This is the asymmetric one of the three. A legitimate git reset --hard or rm -rf is part of normal work, which is why they open when you're explicit. Writing to the session transcript almost never has a legitimate reason. Blocking it by default is exactly what you want.
4. See the exact rules
The full text of each rule lives in the classifier defaults:
claude auto-mode defaults
As of v2.1.208 you can read a single one by its label, without piping through jq:
claude auto-mode defaults --label 'Session Transcript Tampering'
The match is a case-insensitive prefix on the label. On older versions, plain claude auto-mode defaults prints all four lists in full.
Reference
Rule (soft_deny) |
What it stops | Clears when | Status |
|---|---|---|---|
Irreversible Local Destruction |
git reset --hard, git clean -fd, git stash drop, rm -rf on pre-session files |
you name the target or ask to discard it | Widened (v2.1.178) |
Unverifiable Deletion Target |
rm -rf "$VAR"/* with $VAR unresolved in the transcript |
you name the path or write it literally into the command | New (v2.1.202) |
Session Transcript Tampering |
writes to the session .jsonl files or forging {"meta"} lines |
almost never (false positive only) | New (v2.1.202) |
When one of these blocks you, the reason shows up next to the action in the transcript, in the denial notification, and in /permissions under the "Recently denied" tab. That's where you decide whether the fix is rewriting the command or retrying with explicit intent.
Requirements
Auto mode enabled. The destructive-git widening landed in v2.1.178; the unresolved rm -rf and the transcript protection in v2.1.202. The --label flag needs v2.1.208 or later.
New to auto mode? Start with Escape Claude Code's permission fatigue without going YOLO. To place it among the six modes, see Control How Much Autonomy Claude Code Gets with 6 Permission Modes.
Official docs: Configure auto mode | Permission modes