Guides

Claude Code a supprimé mes fichiers ? La couche qui l'en empêche

Une protection contre la suppression dans Claude Code repose sur quatre couches qui empêchent un agent d'effacer vos fichiers : une demande d'approbation, une règle de refus, un hook PreToolUse et un travail commité et poussé. ConvOps ajoute des exécutions isolées et une validation avant le merge pour les exécutions que personne ne surveille.

DM

David Marsa

Founder & CEO

Débutant30 min de lecturePublié le 9 oct. 2026

Dernière vérification : 9 oct. 2026

Outils et modèles abordés:Claude CodeOpenCodeKimi K3
Un fichier tombe vers un broyeur en passant quatre gardes : une main levée pour l'approbation, que les exécutions sans surveillance sautent, une grille de refus, un portique à capteur pour le hook et un bac qui contient la copie poussée.

Ce que vous saurez faire

  • Savoir ce qui revient après une suppression (travail commité, fichiers archivés) et ce qui ne revient pas.
  • Ajouter un bloc de refus aux paramètres de Claude Code et connaître les formes de suppression qu'il laisse passer.
  • Écrire un hook PreToolUse qui bloque les suppressions et indique à l'agent quoi faire à la place, puis le tester avec un exemple de JSON.
  • Choisir les couches qui protègent vos exécutions sans surveillance, sachant qu'elles sautent les demandes d'approbation.
  • Appliquer la même forme de protection dans OpenCode et Kimi Code CLI.

Points clés

  • Pour empêcher Claude Code de supprimer vos fichiers, bloquez les suppressions avec une règle de refus et un hook PreToolUse, puis commitez et poussez votre travail pour qu'une suppression non détectée ait un chemin de retour.
  • Lors de nos tests dans des dossiers jetables avec --dangerously-skip-permissions, une règle de refus et un hook PreToolUse de Claude Code ont chacun bloqué rm ; les demandes d'approbation n'apparaissent jamais dans les exécutions sans surveillance.
  • Les règles de refus comparent le texte des commandes : find -delete, /bin/rm et un one-liner Python leur ont échappé ; notre hook a intercepté les deux premiers et raté le troisième, car un hook ne bloque que ce que ses motifs nomment.
  • Un message de blocage qui nomme l'alternative sûre (déplacer les fichiers dans le dossier _archive/ du projet) évite que l'agent improvise pour contourner le blocage.
  • Un hook qui compare du texte bloque aussi des commandes inoffensives ; testez-le avec des cas de test et acceptez le faux positif plutôt qu'une suppression non détectée.

Si Claude Code a supprimé vos fichiers, seul ce qui existe ailleurs revient. Pour empêcher Claude Code de supprimer à nouveau des fichiers, poussez votre travail, puis ajoutez une règle de refus et un hook. Ce guide met en place chaque couche pour Claude Code en 30 minutes environ et montre ce que chacune laisse passer.

Nous faisons tourner une protection contre la suppression sur chaque session Claude Code de notre propre dépôt. Les exemples utilisent Marrowby Tiles, un magasin de carrelage fictif ; les règles et les motifs reprennent les nôtres.

Claude Code a supprimé des fichiers : ce qui revient

Le chemin du retour se construit avant la suppression, jamais après.

RevientReste perdu
Les fichiers commités (en local ou poussés) ; seuls les commits poussés survivent à un dossier perdu ou effacéLes fichiers non commités supprimés par rm, find -delete ou un script
Les fichiers qu'un hook a redirigés vers _archive/ au lieu de les supprimerLes modifications non commitées effacées par git checkout --, git reset --hard ou git restore

Vous venez de perdre des fichiers ? Procédez dans cet ordre :

  1. Regardez dans le dossier _archive/ du projet.
  2. Lancez git status pour lister les fichiers suivis manquants.
  3. Commité mais pas poussé ? git restore --source=HEAD --staged --worktree <path> le récupère depuis votre dernier commit local.
  4. Lancez git fetch, puis git restore --source=origin/main <path> pour récupérer un fichier dans son dernier état poussé (remplacez main par le nom de votre branche).
  5. Si un commit a supprimé le fichier, git log --diff-filter=D -- <path> indique ce commit.

Tout ce qui échappe à ces étapes a disparu du disque.

Pourquoi Claude Code supprime des fichiers

Une demande d'approbation protège la personne devant le clavier. Elle ne fait rien pour une exécution à 3 h du matin.

Les agents suppriment pour des raisons banales : quand on lui demande de « repartir de zéro », Claude Code vide d'abord le dossier, et lors de notre test dans un dossier jetable, il a choisi find -delete de lui-même. Git supprime aussi : git reset --hard, git clean, git restore, git checkout -- et git stash drop jettent le travail non commité, et un simple git stash le cache jusqu'à ce que vous le récupériez.

La demande d'approbation est la couche la plus faible. On approuve ce qu'on survole, et nos exécutions planifiées d'agents utilisent --dangerously-skip-permissions : aucune demande n'apparaît.

IndicateurValeurSource
Commandes dangereuses repérées par des relecteurs humains dans une étude contrôlée13,6 %Anthropic(s’ouvre dans un nouvel onglet), 2026
Commandes dangereuses repérées par le classifieur du mode auto de Claude Code, même étude89 %Anthropic(s’ouvre dans un nouvel onglet), 2026
Fichiers que Claude Code aurait supprimés chez un développeur48 000 en 103 secondesTechRadar(s’ouvre dans un nouvel onglet), 2026

Découvrez comment ConvOps place une étape de validation avant chaque merge(s’ouvre dans un nouvel onglet) : une suppression faite pendant une exécution sans surveillance n'atteint jamais main sans le feu vert d'une personne.

Quelle couche arrête vraiment une suppression

La liste de refus est un ralentisseur. Le hook est un mur aux failles connues. Le travail poussé est le socle.

Qu'est-ce qu'une règle de refus ? Une règle de refus (deny) est une ligne des paramètres de Claude Code qui refuse toute commande Bash dont le texte correspond à un motif, comme Bash(rm *).

Qu'est-ce qu'un hook PreToolUse ? Un hook PreToolUse est un script que Claude Code exécute avant un appel d'outil ; quand le script se termine avec le code 2, l'appel est bloqué et l'agent lit le message du script.

Quatre couches séparent la suppression lancée par un agent de vos fichiers, et seule la demande d'approbation saute quand personne ne regarde.

Lors de nos tests dans des dossiers jetables, une règle de refus et un hook ont chacun bloqué rm à eux seuls. Aucun des deux n'a tout arrêté.

Chaque couche, seule, laisse passer quelque chose : une suppression par find passe la règle de refus, un one-liner Python passe le hook, et la couche suivante prend le relais.

Commiter et pousser avant que l'agent ne touche à quoi que ce soit

Le travail non poussé meurt avec le dossier qui le contient.

Avant de lancer Claude Code, enregistrez tout sur le dépôt distant :

git status
git add src/ data/products.json
git commit -m "Save work before the agent run"
git push

Ne lancez l'agent que sur une arborescence propre et à jour avec le dépôt distant. Nos workflows poussent le résultat de chaque étape avant de passer à la suivante, et un hook d'arrêt empêche de clore une session qui laisse du travail non poussé. Exécutez le travail risqué dans un worktree git, comme ~/marrowby-shop-run-0412, ou dans une exécution isolée qui part du dépôt distant.

Ajouter des règles de refus à Claude Code

Les règles de refus comparent le texte de la commande, pas ce que fait la commande.

Ce bloc va dans le fichier .claude/settings.json du projet :

{
  "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 *) et l'ancienne forme Bash(rm:*) se sont comportées de la même façon sur la 2.1.295, même avec --dangerously-skip-permissions. La documentation des modes de permission(s’ouvre dans un nouvel onglet) d'Anthropic indique que les règles de refus « bloquent dans tous les modes, y compris bypassPermissions », et range le mode auto parmi ces modes. La documentation des permissions(s’ouvre dans un nouvel onglet) précise aussi qu'une règle de refus « n'est pas une frontière de sécurité autour du programme ». Lors de notre test, find -delete, /bin/rm et un one-liner python3 -c ont chacun supprimé le fichier ; sh -c 'rm ...' a été intercepté.

Ajouter un hook qui bloque et propose une issue

Un blocage sans alternative apprend à l'agent à vous contourner.

Le fichier .claude/settings.json complet, avec une clé hooks à côté de permissions (guide des hooks(s’ouvre dans un nouvel onglet)) :

{
  "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} pointe vers la racine du projet, si bien que le chemin reste valable quand l'agent change de dossier. Un mauvais chemin de script fait sortir python3 avec le code 2 : chaque appel Bash est alors bloqué avec une erreur can't open file. Le blocage se fait donc par défaut (fail closed), et la première commande le révèle.

Et le script, .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())

Chaque ligne a sa raison d'être :

  • Le code de sortie 2 bloque l'appel ; l'agent lit stderr.
  • (?<!-)\brm\b intercepte tout jeton rm, /bin/rm compris. Le lookbehind laisse passer docker run --rm.
  • Une entrée illisible sort avec 0 : elle ne bloque jamais une session, et elle passe sans contrôle.
  • Aucun motif pour les interpréteurs. Les one-liners os.remove et shutil.rmtree passent : le hook ne bloque que ce que ses motifs nomment.

Un hook s'exécute à chaque appel Bash, quelle que soit la consigne donnée à l'agent (graph engineering). C'est pourquoi une règle comme celle-ci a sa place dans un hook, et non dans un CLAUDE.md qui ne cesse de grossir.

Tester le hook avant de s'y fier

Une regex non testée n'est qu'une supposition.

Envoyez un exemple de payload au script :

echo '{"tool_name":"Bash","tool_input":{"command":"find exports -mindepth 1 -delete"}}' \
  | python3 .claude/hooks/delete-guard.py; echo "exit $?"

Ajoutez un cas de test par forme, y compris les ratés attendus :

CommandeRésultat attendu
rm -rf exports/sortie 2
git checkout -- src/sortie 2
docker run --rm alpine truesortie 0
git commit -m "Never rm -rf exports, archive instead"sortie 0
python3 -c "import os; os.remove('exports/product-feed.csv')"sortie 0, un raté connu
git push -f origin mainsortie 2
grep -rn "rm -rf" scripts/sortie 2, un blocage excessif connu

Terminez par un blocage en conditions réelles : dans un dossier jetable, demandez à Claude Code de supprimer un fichier sans importance.

Une tentative de suppression, retracée

Le meilleur blocage est celui dont l'agent se remet seul.

Le hook bloque la suppression par find de l'agent avec le code 2, son message nomme le dossier _archive/ du projet, et l'agent y déplace les fichiers à la place : rien n'est supprimé.

  1. Déclencheur. Marrowby Tiles demande : « Régénérez l'export du flux produits, en repartant de zéro. » L'agent lance find exports -mindepth 1 -delete (repère 1).
  2. Blocage. Le hook détecte \bfind\b.*\s-delete\b et sort avec le code 2 ; son message nomme _archive/ dans ce projet (repère 2).
  3. Redirection. L'agent lance mkdir -p _archive && mv exports/* _archive/ (repère 3).
  4. Verdict. 2 éléments déplacés, 0 supprimé : _archive/product-feed.csv et _archive/old/ (repère 4).

Ce qui tourne mal avec une protection contre la suppression

Nous préférons expliquer un faux blocage plutôt que restaurer un dossier.

Une recherche en lecture seule de « rm -rf » est bloquée parce que le hook compare du texte, et nous gardons ce faux positif exprès : mieux vaut un faux blocage qu'une suppression non détectée.

Quatre échecs que nous avons rencontrés, chacun avec sa règle :

  • Une recherche a été bloquée. Un grep -rn "rm -rf" scripts/ en lecture seule a déclenché le motif. Règle : garder le faux positif (fail closed) et son cas de test.
  • Des messages de commit ont déclenché le hook. « Never rm -rf exports » a été détecté comme une commande. Règle : retirer d'abord les arguments -m et --message entre guillemets.
  • Le chemin d'archive sortait du projet. Notre message de blocage nommait un dossier à la racine de notre dépôt, et un agent de test dans un autre projet y a déplacé ses fichiers. Règle : nommer _archive/ dans ce projet.
  • Une commande git a effacé du travail. git checkout -- sur deux dossiers a effacé du travail non commité. Règle : refuser et intercepter aussi les commandes git qui suppriment.

Quand personne ne regarde

Les demandes d'approbation ne protègent pas les exécutions sans surveillance. Les validations et l'isolation, si.

Nos exécutions sans surveillance sautent volontairement les demandes d'approbation. Trois contrôles les encadrent :

ContrôleCe qu'il faitCe qu'il ne fait pas
Règles de refus et hookBloquent les formes de suppression qu'ils nomment lors des exécutions localesIntercepter les formes qu'ils ne nomment pas
Exécution isoléeLance chaque traitement dans son propre environnement, comme marrowby-run, à partir des commits poussés : une suppression ne peut pas atteindre votre copieArrêter une commande à l'intérieur de l'exécution
Étape de validation avant le mergeUne personne approuve avant qu'une modification, suppression comprise, n'atteigne mainArrêter une commande pendant l'exécution

Nos workflows s'arrêtent pour une validation là où se trouve le risque (humain dans la boucle), et notre boucle de réparation du site tourne dans un environnement isolé.

Copiez le bloc de refus et le hook dès aujourd'hui. Ensuite, exécutez vos traitements d'agent sans surveillance sous forme de tâches ConvOps(s’ouvre dans un nouvel onglet), avec une étape de validation avant le merge et une exécution isolée.

La même protection dans OpenCode et Kimi Code CLI

Une seule forme de protection, trois fichiers.

Nous avons testé cette protection dans des projets jetables ; notre protection au quotidien tourne dans Claude Code.

La même vérification avant exécution se trouve dans le fichier propre à chaque outil : un hook PreToolUse dans Claude Code, un plugin tool.execute.before dans OpenCode et une entrée hooks dans Kimi Code CLI.

OpenCode 1.18.31

Dans OpenCode, les règles de refus de permission.bash ont arrêté rm et find -delete, puis l'agent a supprimé avec un one-liner Python. Un plugin, .opencode/plugins/delete-guard.js, vérifie chaque commande bash dans tool.execute.before, avec os.remove et shutil.rmtree parmi ses motifs, et lève une erreur dont le message nomme _archive/. Il a bloqué les six formes que nous avons essayées, et l'agent a archivé à la place. opencode run --pure ignore les plugins, protection comprise.

Kimi Code CLI 0.31.1

Kimi Code CLI (qui fait tourner des modèles comme Kimi K3) accepte un hook PreToolUse dans ~/.kimi-code/config.toml :

[[hooks]]
event = "PreToolUse"
matcher = "Bash"
command = "node ~/.kimi-code/hooks/delete-guard.mjs"

Le script sort avec le code 2 et le même message et, en mode yolo, a bloqué les six formes. Les hooks se trouvent uniquement dans la configuration utilisateur, jamais dans le projet. La règle native Bash(rm *) n'a jamais intercepté un chemin contenant une barre oblique ; Bash(rm */**), si.

Quel que soit votre outil, l'ordre est le même : pousser, refuser, intercepter, tester.

Étapes

  1. Commiter et pousser votre travail

    Lancez git status, commitez tout et poussez avant de démarrer Claude Code. Exécutez le travail risqué dans un worktree git ou dans une exécution isolée qui part du dépôt distant. Livrable : rien de ce qui compte pour vous n'existe uniquement sur votre disque.

  2. Ajouter des règles de refus à Claude Code

    Collez un bloc permissions.deny dans le fichier .claude/settings.json du projet, qui couvre rm, rmdir, git rm, git clean, git reset, git restore, git checkout sur un chemin, git stash drop et clear, et le force push. Livrable : Claude Code refuse ces commandes, même avec --dangerously-skip-permissions.

  3. Ajouter un hook PreToolUse qui indique la voie sûre

    Enregistrez delete-guard.py dans .claude/hooks/ et branchez-le comme hook PreToolUse avec le matcher Bash. Livrable : une commande détectée sort avec le code 2 et l'agent lit un message qui lui demande de déplacer les fichiers dans _archive/, dans ce projet.

  4. Tester le hook avec un exemple de JSON

    Envoyez au script un exemple de payload pour chaque forme de suppression et vérifiez le code de sortie : 2 pour les suppressions, 0 pour docker run --rm et pour les messages de commit. Déclenchez ensuite un blocage en conditions réelles dans un dossier jetable. Livrable : une liste de cas de test qui consigne aussi le raté connu et le blocage excessif connu.

  5. Isoler les exécutions sans surveillance

    Lancez les traitements d'agent planifiés dans un environnement isolé qui part des commits poussés, avec une étape de validation avant le merge. Livrable : une suppression pendant une exécution n'atteint ni votre copie ni main sans le feu vert d'une personne. Créez un compte ConvOps gratuit sur https://my.convops.app/register pour les exécuter sous forme de tâches.

Questions fréquentes

Qu'est-ce qui empêche Claude Code de supprimer des fichiers ?

Quatre couches empêchent Claude Code de supprimer des fichiers : une demande d'approbation, une règle de refus dans .claude/settings.json, un hook PreToolUse qui vérifie chaque appel Bash, et un chemin de retour fait de travail commité et poussé et d'exécutions isolées. Seules les trois dernières fonctionnent dans les exécutions sans surveillance, qui sautent les demandes d'approbation. Une règle de refus compare le texte des commandes : find avec -delete et /bin/rm lui échappent, et un hook ne bloque que les motifs qu'il nomme.

Qui a besoin d'empêcher Claude Code de supprimer des fichiers ?

Les développeurs et les équipes qui font tourner Claude Code sur de vrais dépôts ont besoin d'une protection contre la suppression, surtout quand des traitements d'agent tournent sans surveillance ou de façon planifiée. Résultat : une suppression est bloquée, ou le travail existe toujours sur le dépôt distant. ConvOps gère ces traitements comme des tâches, chacune dans une exécution isolée, avec une étape de validation avant le merge : une suppression faite pendant une exécution n'atteint ni votre copie locale ni main sans le feu vert d'une personne.

Claude a-t-il supprimé des fichiers ?

Pour savoir si Claude Code a supprimé des fichiers, relisez les appels à l'outil Bash de la session à la recherche de formes de suppression : rm, rmdir, find avec -delete, git checkout sur un chemin, git reset --hard, git clean ou un one-liner python3 -c. Lancez ensuite git status pour voir quels fichiers suivis manquent. Un fichier supprimé par une commande Bash a disparu du disque, sauf s'il avait été commité (poussé ou en local) ou si un hook l'a déplacé dans un dossier _archive/ au lieu de le supprimer.

Comment empêcher Claude de supprimer des fichiers ?

Commitez et poussez votre travail avant que Claude Code ne démarre. Ajoutez ensuite des règles de refus pour rm, rmdir et les commandes git destructrices dans .claude/settings.json, ajoutez un hook PreToolUse qui sort avec le code 2 et demande à l'agent de déplacer les fichiers dans _archive/ à la place, et testez le hook en lui envoyant un exemple de JSON. Enfin, lancez les traitements sans surveillance dans des exécutions isolées. Les règles de refus ne comparent que le texte des commandes : le hook et le travail poussé couvrent ce qu'elles laissent passer.

Pourquoi Claude a-t-il supprimé mes projets ?

Claude Code supprime des fichiers quand une tâche ressemble à une remise à zéro : quand on lui demande de repartir de zéro, il vide d'abord le dossier, et lors de notre test, il a choisi find avec -delete de lui-même. Les commandes git qui annulent du travail, comme git checkout sur un chemin, git reset --hard ou git clean, suppriment aussi les modifications non commitées. Sans règle de refus ni hook, rien ne s'interpose entre la commande et le disque.

Claude peut-il récupérer des fichiers supprimés ?

Claude Code ne récupère que ce qui existe en dehors des fichiers de travail : les commits, locaux ou poussés (seuls les commits poussés survivent à un dossier perdu ou effacé), et les fichiers qu'un hook a redirigés vers un dossier _archive/ au lieu de les supprimer. Le travail non commité supprimé par une commande Bash ou annulé par git reste perdu. Les exécutions isolées de ConvOps partent des commits poussés, dans leur propre environnement : une suppression à l'intérieur de l'une d'elles ne peut pas atteindre votre copie locale.

Les règles de refus fonctionnent-elles encore en mode bypassPermissions ?

Oui. Lors de nos tests dans des dossiers jetables sur Claude Code 2.1.295 avec --dangerously-skip-permissions, la règle de refus Bash(rm *) a bloqué rm, et un hook PreToolUse sortant avec le code 2 l'a bloqué aussi, ce qui correspond à la documentation d'Anthropic sur les modes de permission. La limite : la même règle de refus a laissé find avec -delete, /bin/rm et un one-liner Python supprimer le fichier, car les règles de refus comparent le texte des commandes, pas ce qu'elles font.