Das können Sie danach
- Wissen, was nach einer Löschung zurückkommt (committete Arbeit, archivierte Dateien) und was nicht.
- Einen Deny-Block in die Einstellungen von Claude Code einfügen und wissen, welche Löschformen er übersieht.
- Einen PreToolUse-Hook schreiben, der Löschungen blockiert und dem Agenten sagt, was er stattdessen tun soll, und ihn mit Beispiel-JSON testen.
- Entscheiden, welche Schichten Ihre unbeaufsichtigten Läufe schützen, da diese Abfragen überspringen.
- Dasselbe Schutzprinzip in OpenCode und Kimi Code CLI anwenden.
Das Wichtigste
- Damit Claude Code Ihre Dateien nicht löscht, blockieren Sie Löschbefehle mit einer Deny-Regel und einem PreToolUse-Hook, und committen und pushen Sie, damit eine übersehene Löschung einen Rückweg hat.
- In unseren Testläufen unter --dangerously-skip-permissions blockierten eine Deny-Regel und ein PreToolUse-Hook in Claude Code jeweils rm; Bestätigungsabfragen erscheinen in unbeaufsichtigten Läufen nie.
- Deny-Regeln prüfen den Befehlstext, deshalb kamen find -delete, /bin/rm und ein Python-Einzeiler an ihnen vorbei; unser Hook fing die ersten beiden ab und übersah den dritten, weil ein Hook nur blockiert, was seine Muster benennen.
- Eine Blockademeldung, die die sichere Alternative nennt (in den Ordner _archive/ des Projekts verschieben), hält den Agenten davon ab, um die Blockade herum zu improvisieren.
- Ein Hook, der Text abgleicht, blockiert auch harmlose Befehle; testen Sie ihn mit Fixtures und nehmen Sie den Fehlalarm lieber in Kauf als eine übersehene Löschung.
Wenn Claude Code Ihre Dateien gelöscht hat, kommt nur zurück, was noch an einem anderen Ort existiert. Damit Claude Code nicht wieder Dateien löscht, pushen Sie Ihre Arbeit und richten dann eine Deny-Regel und einen Hook ein. Diese Anleitung baut jede Schicht für Claude Code in etwa 30 Minuten auf und zeigt, was jede einzelne übersieht.
Wir betreiben einen Löschschutz in jeder Claude-Code-Session in unserem eigenen Repository. Die Beispiele nutzen Marrowby Tiles, einen erfundenen Fliesenhändler; die Regeln und Muster entsprechen unseren.
Claude Code hat Dateien gelöscht: Was zurückkommt
Der Rückweg entsteht vor dem Löschen, nie danach.
| Kommt zurück | Bleibt weg |
|---|---|
| Committete Dateien (lokal oder gepusht); nur gepushte Commits überstehen einen verlorenen oder geleerten Ordner | Nicht committete Dateien, die rm, find -delete oder ein Skript entfernt hat |
Dateien, die ein Hook nach _archive/ umgeleitet hat, statt sie zu löschen | Nicht committete Änderungen, die git checkout --, git reset --hard oder git restore verworfen hat |
Gerade Dateien verloren? Gehen Sie in dieser Reihenfolge vor:
- Sehen Sie im Ordner
_archive/des Projekts nach. - Führen Sie
git statusaus, um fehlende versionierte Dateien aufzulisten. - Committet, aber nicht gepusht?
git restore --source=HEAD --staged --worktree <path>holt die Datei aus Ihrem letzten lokalen Commit zurück. - Führen Sie
git fetchund danngit restore --source=origin/main <path>aus, um eine Datei im zuletzt gepushten Stand zurückzuholen (setzen Sie den Namen Ihres Branches ein). - Hat ein Commit die Datei entfernt, nennt
git log --diff-filter=D -- <path>diesen Commit.
Was diese Schritte nicht finden, ist von der Festplatte verschwunden.
Warum Claude Code Dateien löscht
Eine Rückfrage schützt die Person an der Tastatur. Für einen Lauf um 3 Uhr nachts tut sie nichts.
Agenten löschen aus ganz gewöhnlichen Gründen: Bittet man Claude Code, „neu anzufangen“, leert es zuerst den Ordner, und in unserem Test in einem Wegwerfordner wählte es von sich aus find -delete. Auch Git löscht: git reset --hard, git clean, git restore, git checkout -- und git stash drop verwerfen nicht committete Arbeit, und ein einfaches git stash versteckt sie, bis Sie sie mit git stash pop zurückholen.
Die Bestätigungsabfrage ist die schwächste Schicht. Menschen bestätigen, was sie nur überfliegen, und unsere geplanten Agentenläufe nutzen --dangerously-skip-permissions, sodass gar keine Abfrage erscheint.
| Kennzahl | Wert | Quelle |
|---|---|---|
| Gefährliche Befehle, die menschliche Prüfer in einer kontrollierten Studie erkannten | 13,6 % | Anthropic(wird in einem neuen Tab geöffnet), 2026 |
| Gefährliche Befehle, die der Klassifikator des Auto-Modus von Claude Code in derselben Studie erkannte | 89 % | Anthropic(wird in einem neuen Tab geöffnet), 2026 |
| Dateien, die Claude Code bei einem Entwickler angeblich gelöscht hat | 48.000 in 103 Sekunden | TechRadar(wird in einem neuen Tab geöffnet), 2026 |
Sehen Sie, wie ConvOps vor jeden Merge einen Freigabeschritt setzt(wird in einem neuen Tab geöffnet), damit eine Löschung aus einem unbeaufsichtigten Lauf main nie ohne das Ja einer Person erreicht.
Welche Schicht eine Löschung wirklich stoppt
Die Deny-Liste ist eine Bremsschwelle. Der Hook ist eine Mauer mit bekannten Lücken. Gepushte Arbeit ist das Fundament.
Was ist eine Deny-Regel? Eine Deny-Regel ist eine Zeile in den Einstellungen von Claude Code, die jeden Bash-Befehl ablehnt, dessen Text auf ein Muster passt, etwa Bash(rm *).
Was ist ein PreToolUse-Hook? Ein PreToolUse-Hook ist ein Skript, das Claude Code vor einem Tool-Aufruf ausführt; endet das Skript mit Exit-Code 2, wird der Aufruf blockiert und der Agent liest die Meldung des Skripts.

Eine Deny-Regel und ein Hook blockierten in unseren Testläufen jeweils für sich allein rm. Keine der beiden stoppte alles.

Committen und pushen, bevor ein Agent etwas anfasst
Ungepushte Arbeit geht mit dem Ordner verloren, in dem sie liegt.
Bevor Sie Claude Code starten, sichern Sie alles auf dem Remote:
git status
git add src/ data/products.json
git commit -m "Save work before the agent run"
git push
Starten Sie den Agenten nur auf einem sauberen Arbeitsverzeichnis, das mit dem Remote auf dem aktuellen Stand ist. Unsere Workflows pushen das Ergebnis jedes Schritts, bevor sie weitergehen, und ein Stop-Hook verhindert, dass eine Session mit ungepushter Arbeit endet. Führen Sie riskante Arbeit in einem Git-Worktree aus, etwa ~/marrowby-shop-run-0412, oder in einem isolierten Lauf, der vom Remote startet.
Deny-Regeln in Claude Code anlegen
Deny-Regeln prüfen den Text eines Befehls, nicht das, was der Befehl tut.
Dieser Block gehört in die .claude/settings.json des Projekts:
{
"permissions": {
"deny": [
"Bash(rm *)",
"Bash(rmdir *)",
"Bash(git rm *)",
"Bash(git clean *)",
"Bash(git reset *)",
"Bash(git restore *)",
"Bash(git checkout -- *)",
"Bash(git stash drop *)",
"Bash(git stash clear *)",
"Bash(git push --force *)",
"Bash(git push -f *)"
]
}
}
Bash(rm *) und das ältere Bash(rm:*) verhielten sich in Version 2.1.295 gleich, auch unter --dangerously-skip-permissions. Laut Anthropics Dokumentation zu den Berechtigungsmodi(wird in einem neuen Tab geöffnet) blockieren Deny-Regeln „in jedem Modus, einschließlich bypassPermissions“, und der Auto-Modus zählt dort zu diesen Modi. Die Dokumentation zu Berechtigungen(wird in einem neuen Tab geöffnet) hält außerdem fest, dass eine Deny-Regel „keine Sicherheitsgrenze um das Programm“ ist. In unserem Lauf löschten find -delete, /bin/rm und ein python3 -c-Einzeiler jeweils die Datei; sh -c 'rm ...' wurde abgefangen.
Einen Hook einrichten, der blockiert und einen Ausweg nennt
Eine Blockade ohne Alternative bringt dem Agenten bei, Sie zu umgehen.
Die vollständige .claude/settings.json mit einem hooks-Schlüssel neben permissions (Hooks-Leitfaden(wird in einem neuen Tab geöffnet)):
{
"permissions": {
"deny": [
"Bash(rm *)",
"Bash(rmdir *)",
"Bash(git rm *)",
"Bash(git clean *)",
"Bash(git reset *)",
"Bash(git restore *)",
"Bash(git checkout -- *)",
"Bash(git stash drop *)",
"Bash(git stash clear *)",
"Bash(git push --force *)",
"Bash(git push -f *)"
]
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3",
"args": ["${CLAUDE_PROJECT_DIR}/.claude/hooks/delete-guard.py"],
"timeout": 5
}
]
}
]
}
}
${CLAUDE_PROJECT_DIR} zeigt auf das Projektverzeichnis, sodass der Pfad auch dann stimmt, wenn der Agent den Ordner wechselt. Ein falscher Skriptpfad lässt python3 mit Exit-Code 2 enden, sodass jeder Bash-Aufruf mit dem Fehler can't open file blockiert wird. Im Fehlerfall wird also blockiert statt durchgelassen, und schon der erste Befehl zeigt es.
Und das Skript, .claude/hooks/delete-guard.py:
import json, re, sys
ARCHIVE = ("file deletion is denied, archive instead: move it to _archive/ "
"in this project (mkdir -p _archive && mv <path> _archive/)")
PATTERNS = [
(r"\brm\s+-rf\b", "rm -rf is irreversible recursive deletion, " + ARCHIVE),
(r"(?<!-)\brm\b", ARCHIVE),
(r"\brmdir\b", ARCHIVE),
(r"\bfind\b.*\s-delete\b", ARCHIVE),
(r"\bgit\s+checkout\s+--\s+", "git checkout -- reverts uncommitted work"),
(r"\bgit\s+reset\b", "git reset can destroy commit history"),
(r"\bgit\s+restore\b", "git restore reverts uncommitted work"),
(r"\bgit\s+clean\b", "git clean deletes untracked files"),
(r"\bgit\s+stash\b", "git stash can drop uncommitted work"),
(r"\bgit\s+push\b.*\s(?:-f|--force)\b", "force push overwrites remote history"),
]
MESSAGE_ARG = re.compile(r"""(?:^|\s)(?:-m|--message)(?:=|\s+)(?:"[^"]*"|'[^']*'|\S+)""")
def main():
try:
command = json.load(sys.stdin)["tool_input"]["command"]
except Exception:
return 0
scanned = MESSAGE_ARG.sub(" ", command)
for pattern, reason in PATTERNS:
if re.search(pattern, scanned):
print(f"BLOCKED: Destructive command detected.\n Reason: {reason}\n"
f" Command: {command}", file=sys.stderr)
return 2
return 0
sys.exit(main())
Jede Zeile hat einen Grund:
- Exit-Code 2 blockiert den Aufruf; der Agent liest stderr.
(?<!-)\brm\berfasst jedesrm-Token, auch/bin/rm. Der Lookbehind lässtdocker run --rmweiterhin zu.- Nicht lesbare Eingaben enden mit 0: So legen sie nie eine Session lahm, bleiben aber ungeprüft.
- Kein Muster für Interpreter. Einzeiler mit
os.removeundshutil.rmtreekommen durch: Der Hook blockiert nur, was seine Muster benennen.
Ein Hook läuft bei jedem Bash-Aufruf, ganz gleich, was man dem Agenten gesagt hat (Graph Engineering). Deshalb gehört eine solche Regel in einen Hook und nicht in eine CLAUDE.md, die immer weiter wächst.
Den Hook testen, bevor Sie ihm vertrauen
Ein ungetesteter regulärer Ausdruck ist eine Vermutung.
Leiten Sie eine Beispiel-Payload in das Skript:
echo '{"tool_name":"Bash","tool_input":{"command":"find exports -mindepth 1 -delete"}}' \
| python3 .claude/hooks/delete-guard.py; echo "exit $?"
Legen Sie für jede Befehlsform eine Fixture an, auch für erwartete Lücken:
| Befehl | Erwartet |
|---|---|
rm -rf exports/ | Exit 2 |
git checkout -- src/ | Exit 2 |
docker run --rm alpine true | Exit 0 |
git commit -m "Never rm -rf exports, archive instead" | Exit 0 |
python3 -c "import os; os.remove('exports/product-feed.csv')" | Exit 0, eine bekannte Lücke |
git push -f origin main | Exit 2 |
grep -rn "rm -rf" scripts/ | Exit 2, eine bekannte Überblockierung |
Schließen Sie mit einer echten Blockade ab: Bitten Sie Claude Code in einem Wegwerfordner, eine Testdatei zu löschen.
Ein Löschversuch, Schritt für Schritt verfolgt
Die beste Blockade ist die, von der sich der Agent selbst erholt.

- Auslöser. Marrowby Tiles gibt den Auftrag: „Produkt-Feed-Export neu erzeugen, komplett von vorn.“ Der Agent führt
find exports -mindepth 1 -deleteaus (Markierung 1). - Blockade. Der Hook erkennt
\bfind\b.*\s-delete\bund endet mit Exit-Code 2; seine Meldung nennt_archive/in diesem Projekt (Markierung 2). - Umleitung. Der Agent führt
mkdir -p _archive && mv exports/* _archive/aus (Markierung 3). - Ergebnis. 2 Elemente verschoben, 0 gelöscht:
_archive/product-feed.csvund_archive/old/(Markierung 4).
Was bei einem Löschschutz schiefgeht
Wir erklären lieber eine unnötige Blockade, als einen Ordner wiederherzustellen.

Vier Fehler, auf die wir gestoßen sind, jeweils mit ihrer Regel:
- Eine Suche wurde blockiert. Ein reines Lese-
grep -rn "rm -rf" scripts/schlug an. Regel: den Fehlalarm behalten (im Zweifel blockieren), ebenso seine Fixture. - Commit-Nachrichten lösten den Hook aus. „Never rm -rf exports“ wurde wie ein Befehl erkannt. Regel:
-m- und--message-Argumente in Anführungszeichen vorher herausfiltern. - Der Archivpfad verließ das Projekt. Unsere Blockademeldung nannte einen Ordner im Wurzelverzeichnis unseres Repositorys, und ein Testagent in einem anderen Projekt verschob seine Dateien dorthin. Regel:
_archive/in diesem Projekt nennen. - Ein Git-Befehl vernichtete Arbeit.
git checkout --auf zwei Ordnern löschte nicht committete Arbeit. Regel: auch die Löschbefehle von Git per Deny-Regel und Hook sperren.
Wenn niemand zusieht
Rückfragen sind keine Kontrolle für unbeaufsichtigte Läufe. Gates und Isolation sind es.
Unsere unbeaufsichtigten Läufe überspringen Rückfragen absichtlich. Drei Kontrollen tragen sie:
| Kontrolle | Was sie tut | Was sie nicht tut |
|---|---|---|
| Deny-Regeln und der Hook | Blockieren in lokalen Läufen die Löschformen, die sie benennen | Formen abfangen, die sie nicht benennen |
| Isolierter Lauf | Führt jeden Job in einer eigenen Umgebung aus, etwa marrowby-run, ausgehend von gepushten Commits, sodass eine Löschung Ihre Kopie nicht erreicht | Einen Befehl innerhalb des Laufs stoppen |
| Freigabeschritt vor dem Merge | Eine Person gibt frei, bevor eine Änderung, auch eine Löschung, main erreicht | Einen Befehl während des Laufs stoppen |
Unsere Workflows halten dort für eine Freigabe an, wo das Risiko liegt (Human in the Loop), und unsere Reparaturschleife für die Website läuft in einer isolierten Umgebung.
Kopieren Sie den Deny-Block und den Hook noch heute. Führen Sie Ihre unbeaufsichtigten Agenten-Jobs dann als ConvOps-Tasks aus(wird in einem neuen Tab geöffnet), mit einem Freigabeschritt vor dem Merge und einem isolierten Lauf.
Derselbe Schutz in OpenCode und Kimi Code CLI
Ein Schutzprinzip, drei Dateien.
Wir haben diesen Schutz in Testprojekten geprüft; im Alltag läuft unser Schutz in Claude Code.

OpenCode 1.18.31
In OpenCode stoppten Deny-Regeln in permission.bash zwar rm und find -delete, doch dann löschte der Agent mit einem Python-Einzeiler. Ein Plugin, .opencode/plugins/delete-guard.js, prüft jeden bash-Befehl in tool.execute.before, mit os.remove und shutil.rmtree unter seinen Mustern, und wirft eine Meldung, die _archive/ nennt. Es blockierte alle sechs Formen, die wir ausprobierten, und der Agent archivierte stattdessen. opencode run --pure überspringt Plugins, den Schutz eingeschlossen.
Kimi Code CLI 0.31.1
Kimi Code CLI (es betreibt Modelle wie Kimi K3) nimmt einen PreToolUse-Hook in ~/.kimi-code/config.toml auf:
[[hooks]]
event = "PreToolUse"
matcher = "Bash"
command = "node ~/.kimi-code/hooks/delete-guard.mjs"
Das Skript endet mit Exit-Code 2 und derselben Meldung und blockierte im yolo-Modus alle sechs Formen. Hooks stehen nur in der Benutzerkonfiguration, nie im Projekt. Die native Regel Bash(rm *) griff nie bei einem Pfad mit Schrägstrich; Bash(rm */**) greift.
Ganz gleich, welches Tool Sie verwenden, die Reihenfolge bleibt dieselbe: pushen, sperren, Hook einrichten, testen.
Schritte
Arbeit committen und pushen
Führen Sie git status aus, committen Sie alles und pushen Sie, bevor Sie Claude Code starten. Führen Sie riskante Arbeit in einem Git-Worktree oder einem isolierten Lauf aus, der vom Remote startet. Ergebnis: Nichts, was Ihnen wichtig ist, existiert nur auf Ihrer Festplatte.
Deny-Regeln in Claude Code anlegen
Fügen Sie einen permissions.deny-Block in die .claude/settings.json des Projekts ein, der rm, rmdir, git rm, git clean, git reset, git restore, git checkout auf einen Pfad, git stash drop und clear sowie Force-Push abdeckt. Ergebnis: Claude Code verweigert diese Befehle, auch unter --dangerously-skip-permissions.
Einen PreToolUse-Hook einrichten, der den sicheren Weg nennt
Speichern Sie delete-guard.py in .claude/hooks/ und binden Sie es als PreToolUse-Hook mit dem Bash-Matcher ein. Ergebnis: Ein passender Befehl endet mit Exit-Code 2, und der Agent liest eine Meldung, die ihn anweist, die Dateien nach _archive/ in diesem Projekt zu verschieben.
Den Hook mit Beispiel-JSON testen
Leiten Sie für jede Löschform eine Beispiel-Payload in das Skript und prüfen Sie den Exit-Code: 2 für Löschungen, 0 für docker run --rm und für Commit-Nachrichten. Lösen Sie dann in einem Wegwerfordner eine echte Blockade aus. Ergebnis: eine Fixture-Liste, die auch die bekannte Lücke und die bekannte Überblockierung festhält.
Unbeaufsichtigte Läufe isolieren
Führen Sie geplante Agenten-Jobs in einer isolierten Umgebung aus, die von gepushten Commits startet, mit einem Freigabeschritt vor dem Merge. Ergebnis: Eine Löschung während eines Laufs erreicht weder Ihre Kopie noch main ohne das Ja einer Person. Erstellen Sie unter https://my.convops.app/register ein kostenloses ConvOps-Konto, um sie als Tasks auszuführen.
Häufige Fragen
Was verhindert, dass Claude Code Dateien löscht?
Vier Schichten verhindern, dass Claude Code Dateien löscht: eine Bestätigungsabfrage, eine Deny-Regel in .claude/settings.json, ein PreToolUse-Hook, der jeden Bash-Aufruf prüft, und ein Rückweg aus committeter, gepushter Arbeit und isolierten Läufen. Nur die letzten drei wirken in unbeaufsichtigten Läufen, die Abfragen überspringen. Eine Deny-Regel prüft den Befehlstext, deshalb kommen find mit -delete und /bin/rm an ihr vorbei, und ein Hook blockiert nur die Muster, die er benennt.
Wer muss verhindern, dass Claude Code Dateien löscht?
Entwickler und Teams, die Claude Code auf echten Repositories einsetzen, brauchen einen Löschschutz, vor allem wenn Agenten-Jobs unbeaufsichtigt oder nach Zeitplan laufen. Das Ergebnis: Eine Löschung wird blockiert, oder die Arbeit existiert weiterhin auf dem Remote. ConvOps führt solche Jobs als Tasks mit einem isolierten Lauf und einem Freigabeschritt vor dem Merge aus, sodass eine Löschung während eines Laufs weder Ihre lokale Kopie noch main ohne das Ja einer Person erreicht.
Hat Claude Dateien gelöscht?
Um festzustellen, ob Claude Code Dateien gelöscht hat, prüfen Sie die Bash-Aufrufe der Session auf Löschformen: rm, rmdir, find mit -delete, git checkout auf einen Pfad, git reset --hard, git clean oder einen python3 -c-Einzeiler. Führen Sie dann git status aus, um zu sehen, welche versionierten Dateien fehlen. Eine Datei, die ein Bash-Befehl entfernt hat, ist von der Festplatte verschwunden, sofern sie nicht committet war (gepusht oder lokal) oder ein Hook sie stattdessen in einen Ordner _archive/ verschoben hat.
Wie verhindere ich, dass Claude Dateien löscht?
Committen und pushen Sie Ihre Arbeit, bevor Claude Code startet. Fügen Sie dann Deny-Regeln für rm, rmdir und destruktive Git-Befehle in .claude/settings.json ein, richten Sie einen PreToolUse-Hook ein, der mit Exit-Code 2 endet und den Agenten anweist, Dateien stattdessen nach _archive/ zu verschieben, und testen Sie den Hook, indem Sie Beispiel-JSON in ihn leiten. Führen Sie unbeaufsichtigte Jobs schließlich in isolierten Läufen aus. Deny-Regeln prüfen nur den Befehlstext, deshalb decken der Hook und gepushte Arbeit ab, was sie übersehen.
Warum hat Claude meine Projekte gelöscht?
Claude Code löscht Dateien, wenn eine Aufgabe nach Zurücksetzen klingt: Bittet man es, neu anzufangen, leert es zuerst den Ordner, und in unserem Test wählte es von sich aus find mit -delete. Git-Befehle, die Arbeit zurücksetzen, etwa git checkout auf einen Pfad, git reset --hard oder git clean, löschen ebenfalls nicht committete Änderungen. Ohne Deny-Regel oder Hook steht nichts zwischen dem Befehl und der Festplatte.
Kann Claude gelöschte Dateien wiederherstellen?
Claude Code holt nur zurück, was außerhalb der Arbeitsdateien existiert: Commits, lokal oder gepusht (nur gepushte überstehen einen verlorenen oder geleerten Ordner), und Dateien, die ein Hook in einen Ordner _archive/ umgeleitet hat, statt sie zu löschen. Nicht committete Arbeit, die ein Bash-Löschbefehl oder ein Git-Zurücksetzen entfernt hat, bleibt verloren. Isolierte Läufe in ConvOps starten von gepushten Commits in einer eigenen Umgebung, sodass eine Löschung darin Ihre lokale Kopie nicht erreichen kann.
Wirken Deny-Regeln auch im bypassPermissions-Modus?
Ja. In unseren Testläufen mit Claude Code 2.1.295 und --dangerously-skip-permissions blockierte die Deny-Regel Bash(rm *) den Befehl rm, und ein PreToolUse-Hook mit Exit-Code 2 blockierte ihn ebenfalls, was zu Anthropics Dokumentation der Berechtigungsmodi passt. Die Einschränkung: Dieselbe Deny-Regel ließ find mit -delete, /bin/rm und einen Python-Einzeiler die Datei löschen, weil Deny-Regeln den Befehlstext prüfen, nicht das, was ein Befehl tut.
