CCLearn

The git guard exists — it is auto-mode only

Claude Code's docs do name git reset --hard, git clean -fd and git checkout -- . as blocked by default, but only by the auto mode classifier: in Manual, dontAsk, or under a broad Bash(git *) allow rule nothing stops them, and /rewind cannot recover the work.

circulating and false

This advice is common and does not hold. The official page saying otherwise is linked below.

What this costs you if it is wrong

Ask and deny rules are prefix globs, not a security boundary: `Bash(git reset --hard*)` does not match `git -C /repo reset --hard`, a shell alias, or the same line buried in a script the model runs with `bash cleanup.sh`. Treat these rules as accident reduction. A blunt deny also blocks legitimate recovery work and nudges you into running the command by hand with less thought than the prompt would have given you, which is why `ask` is the better default here.

You copied the PreToolUse hook from the hooks docs. It greps the command for rm -rf and exits 2. You now believe destructive shell commands are covered, and you are running in Manual mode with Bash(git *) on your allow list because you got tired of approving git status.

That combination is the hole. The commands that actually erase unpushed work are not rm -rf. They are git reset --hard, git clean -fd, and git checkout -- . — commands that look like the safe, routine thing to run, so they slip past your attention as easily as the model's.

The correction

The widely repeated version of this tip says the official guidance only ever names rm -rf and never mentions the git commands. That is false, and the false version is worth correcting because it sends you off to build a guard you may already have.

The permission modes page lists, under what the auto mode classifier blocks by default: git reset --hard, git checkout -- ., git restore ., git clean -fd, git stash drop, git stash clear — described as commands the classifier presumes would discard uncommitted changes. The same page says Claude Code runs git status itself before a command that would discard uncommitted work, and shows the classifier whether staged, modified, or untracked work is present — reporting untracked files even when the repo sets status.showUntrackedFiles=no. The permissions page uses Bash(git clean *) as its worked example of an ask rule that survives subshells and command substitution.

So the knowledge is there. The gap is one of scope.

Where you actually stand, by mode

ModeWhat stops git reset --hardThe gap
Manual (default)A permission prompt, like any command outside the read-only setA broad allow rule such as Bash(git *) matches every git subcommand and approves it with no prompt
AutoClassifier blocks it by default; broad allow rules are suspended on entryA narrow allow rule still skips the classifier, so a destructive argument its prefix did not anticipate goes unseen
dontAskEverything that would otherwise prompt is auto-denied, and Claude Code "denies calls matching your explicit ask rules rather than prompting"Your allow rules and PreToolUse approvals are the only routes through, so a broad Bash(git *) allow rule is the entire exposure
bypassPermissionsDeny rules, which "block in every mode, including bypassPermissions", and explicit ask rules, which no mode auto-approvesAllow rules have no effect here, so nothing you permit helps; only what you restrict does

Two of those rows deserve reading twice. The permissions docs show Bash(git * main) matching git merge main and git push origin main — the wildcard stands in for the subcommand and every option before it. A Bash(git *) allow rule you added for convenience is a standing approval for git reset --hard.

And in auto mode, the narrow-rule hole has a named fix: setting autoMode.classifyAllShell suspends every shell allow rule for the session so the classifier sees every command. Requires v2.1.193 or later.

Rewind will not save you

The checkpointing page is unambiguous: checkpointing does not track files modified by bash commands, and those modifications cannot be undone through rewind. Only edits made through the file-editing tools are tracked.

git reset --hard is a bash command. So is git clean -fd. Every checkpoint you have is intact and none of them contains the work.

What to write

Prefer ask over deny. You genuinely need these commands sometimes, and a deny you have to keep working around gets deleted or bypassed.

json
{
  "permissions": {
    "ask": [
      "Bash(git reset --hard*)",
      "Bash(git clean *)",
      "Bash(git checkout -- *)",
      "Bash(git restore *)",
      "Bash(git stash drop*)",
      "Bash(git stash clear*)"
    ]
  }
}

Three things make ask and deny rules better than they look. They match past a leading variable assignment. They apply when any subcommand matches — including inside a subshell, a command substitution, or a loop body, so cd /tmp && git clean -f and echo "$(git clean -f)" both prompt. And they are the one lever that does not evaporate when you change mode: an explicit ask rule sits in the docs' list of actions no mode auto-approves, bypassPermissions included, and in dontAsk a matching ask rule becomes an outright denial rather than a prompt nobody is there to answer.

If you want the hook as well, widen the pattern the docs' example script greps for beyond rm -rf to cover git reset --hard, git clean -f, git checkout --, and the PowerShell Remove-Item -Recurse -Force form. Note that hooks and permission rules fail differently: a deny from a hook is authoritative in every mode including dontAsk, but the hook only sees the command string, same as the glob.

What is unverified here

Nobody has run this. The doc sentences above are quoted from the shipped documentation and are solid; the mode-by-mode table is derived from them rather than observed, and derivation is where errors hide. Specifically untested: whether a Bash(git *) allow rule really swallows git reset --hard in a live Manual-mode session, and whether a Bash(git reset --hard*) ask rule fires on git -C /some/path reset --hard — the documented anchoring says no, which is the whole reason for the caveat below.

The incident reports behind this tip are user-filed accounts of lost work, not reproductions. Version behavior in this area has moved repeatedly, and several of the guarantees above carry explicit minimum versions.

What would settle it: add the ask rules, open a scratch repo with one uncommitted file, and ask Claude to run each of the six commands plus a git -C variant. Whether you get a prompt is the answer.

Checkpoint

  1. You are in Manual mode with Bash(git *) allowed. What happens when Claude runs git reset --hard HEAD~2?

    Show answer

    It runs, with no prompt, and the uncommitted work goes with it. Bash(git *) is a prefix glob whose wildcard stands in for the subcommand and every option before it — the permissions page shows Bash(git * main) matching both git merge main and git push origin main — so the rule you added to stop approving git status is a standing approval for git reset --hard.

    What would have stopped it lives somewhere you are not. The permission modes page (code.claude.com/docs/en/permission-modes) lists git reset --hard, git checkout -- ., git restore ., git clean -fd, git stash drop and git stash clear as blocked by default by the auto mode classifier — which is the page that refutes the widely repeated claim that the official guidance only ever names rm -rf. That coverage is auto mode's, not Claude Code's in general, and Manual does not have it. Honest caveat: the mode-by-mode reading is derived from those sentences, not observed; nobody has watched a broad allow rule swallow this command in a live Manual session.

  2. Your work was destroyed by a bash command two prompts ago. Which /rewind option recovers it?

    Show answer

    None of them. The checkpointing page is unambiguous: checkpointing does not track files modified by bash commands, and those modifications cannot be undone through rewind — only edits made through the file-editing tools are tracked. git reset --hard and git clean -fd are bash commands, so every checkpoint you have is intact and none of them contains the work. This is a case you have to prevent, not recover.

  3. Why does Bash(git clean *) as an ask rule beat the same string as a deny rule for most people?

    Show answer

    Because a deny you keep working around gets deleted or bypassed. You genuinely need these commands sometimes; a blunt deny blocks legitimate recovery work and nudges you into running the thing by hand with less thought than the prompt would have given it. An ask rule also survives a change of mode: no mode auto-approves an explicit ask rule, bypassPermissions included, and in dontAsk a matching ask rule becomes an outright denial rather than a prompt nobody is there to answer. What it is not is a security boundary — these are * globs, so git -C /repo clean -f, an alias, or the same line inside a script the model runs with bash cleanup.sh may never match.

Where this came from

What the docs actually say