O que vai conseguir fazer
- Saber o que se recupera depois de uma eliminação (trabalho com commit, ficheiros arquivados) e o que não se recupera.
- Acrescentar um bloco de negação às definições do Claude Code e saber que formatos de eliminação lhe escapam.
- Escrever um hook PreToolUse que bloqueia eliminações e diz ao agente o que fazer em vez disso, e testá-lo com JSON de exemplo.
- Decidir que camadas protegem as suas execuções sem supervisão, sabendo que estas ignoram os pedidos de aprovação.
- Aplicar o mesmo formato de proteção no OpenCode e no Kimi Code CLI.
Pontos-chave
- Para impedir que o Claude Code apague os seus ficheiros, bloqueie as eliminações com uma regra de negação e um hook PreToolUse, e faça commit e push para que uma eliminação que escape tenha caminho de volta.
- Nas nossas execuções de teste com --dangerously-skip-permissions, tanto uma regra de negação do Claude Code como um hook PreToolUse bloquearam o rm; os pedidos de aprovação nunca aparecem em execuções sem supervisão.
- As regras de negação comparam o texto do comando, por isso find -delete, /bin/rm e um one-liner em Python passaram por elas; o nosso hook apanhou os dois primeiros e falhou o terceiro, porque um hook só bloqueia o que os seus padrões indicam.
- Uma mensagem de bloqueio que indica a alternativa segura (mover para a pasta _archive/ do projeto) impede o agente de improvisar à volta do bloqueio.
- Um hook que compara texto acaba por bloquear comandos inofensivos; teste-o com casos de teste e aceite o falso positivo em vez de uma eliminação que escapa.
Se o Claude Code apagou os seus ficheiros, só recupera o que existe noutro sítio. Para impedir que o Claude Code volte a apagar ficheiros, faça push do seu trabalho e depois acrescente uma regra de negação e um hook. Este guia configura cada camada no Claude Code em cerca de 30 minutos e mostra o que cada uma deixa passar.
Usamos uma proteção contra eliminações em todas as sessões do Claude Code no nosso próprio repositório. Os exemplos usam a Marrowby Tiles, uma loja de azulejos fictícia; as regras e os padrões são iguais aos nossos.
O Claude Code apagou ficheiros: o que se recupera
O caminho de volta constrói-se antes da eliminação, nunca depois.
| Recupera-se | Perde-se de vez |
|---|---|
| Ficheiros com commit (local ou com push); só os commits com push sobrevivem a uma pasta perdida ou apagada | Ficheiros sem commit eliminados por rm, find -delete ou um script |
Ficheiros que um hook desviou para _archive/ em vez de os eliminar | Alterações sem commit apagadas por git checkout --, git reset --hard ou git restore |
Acabou de perder ficheiros? Execute estes passos por ordem:
- Procure na pasta
_archive/do projeto. - Execute
git statuspara listar os ficheiros versionados em falta. - Tem commit mas não tem push?
git restore --source=HEAD --staged --worktree <path>recupera-o a partir do seu último commit local. - Execute
git fetche depoisgit restore --source=origin/main <path>para recuperar um ficheiro tal como estava no último push (substitua pelo nome do seu branch). - Se foi um commit que removeu o ficheiro,
git log --diff-filter=D -- <path>indica qual.
O que estes passos não apanharem desapareceu do disco.
Porque é que o Claude Code apaga ficheiros
Um pedido de aprovação protege quem está ao teclado. Não faz nada por uma execução às 3 da manhã.
Os agentes apagam por motivos banais: quando lhe pedem para «começar do zero», o Claude Code esvazia primeiro a pasta, e no nosso teste descartável escolheu find -delete por iniciativa própria. O Git também apaga: git reset --hard, git clean, git restore, git checkout -- e git stash drop deitam fora trabalho sem commit, e um simples git stash esconde-o até fazer pop.
O pedido de aprovação é a camada mais fraca. As pessoas aprovam o que leem na diagonal, e as nossas execuções agendadas de agentes usam --dangerously-skip-permissions, por isso não aparece pedido nenhum.
| Métrica | Valor | Fonte |
|---|---|---|
| Comandos perigosos detetados por revisores humanos num estudo controlado | 13,6 % | Anthropic(abre num novo separador), 2026 |
| Comandos perigosos detetados pelo classificador do modo automático do Claude Code, no mesmo estudo | 89 % | Anthropic(abre num novo separador), 2026 |
| Ficheiros que o Claude Code terá apagado a um programador | 48.000 em 103 segundos | TechRadar(abre num novo separador), 2026 |
Veja como o ConvOps coloca um passo de aprovação antes de cada merge(abre num novo separador), para que uma eliminação feita durante uma execução sem supervisão nunca chegue ao main sem o sim de uma pessoa.
Que camada trava mesmo uma eliminação
A lista de negação é uma lomba. O hook é um muro com falhas conhecidas. O trabalho com push é o chão.
O que é uma regra de negação? Uma regra de negação (deny) é uma linha nas definições do Claude Code que recusa qualquer comando Bash cujo texto corresponda a um padrão, como Bash(rm *).
O que é um hook PreToolUse? Um hook PreToolUse é um script que o Claude Code executa antes de uma chamada a uma ferramenta; quando o script termina com o código 2, a chamada é bloqueada e o agente lê a mensagem do script.

Nas nossas execuções de teste, tanto a regra de negação como o hook bloquearam sozinhos o rm. Nenhum dos dois travou tudo.

Faça commit e push antes de um agente tocar em alguma coisa
O trabalho sem push morre com a pasta onde está.
Antes de iniciar o Claude Code, guarde tudo no remoto:
git status
git add src/ data/products.json
git commit -m "Save work before the agent run"
git push
Inicie o agente apenas numa árvore limpa e atualizada com o remoto. Os nossos fluxos de trabalho fazem push do resultado de cada passo antes de avançar, e um stop hook impede que uma sessão termine com trabalho sem push. Execute o trabalho arriscado numa git worktree, como ~/marrowby-shop-run-0412, ou numa execução isolada que parte do remoto.
Acrescente regras de negação ao Claude Code
As regras de negação comparam o texto do comando, não o que o comando faz.
Este bloco vai no .claude/settings.json do projeto:
{
"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 *) e o formato mais antigo Bash(rm:*) comportaram-se da mesma forma na versão 2.1.295, mesmo com --dangerously-skip-permissions. A documentação dos modos de permissão(abre num novo separador) da Anthropic diz que as regras de negação «bloqueiam em todos os modos, incluindo bypassPermissions», e inclui o modo automático nesses modos. A documentação de permissões(abre num novo separador) diz também que uma regra de negação «não é uma fronteira de segurança em torno do programa». No nosso teste, find -delete, /bin/rm e um one-liner python3 -c apagaram o ficheiro; sh -c 'rm ...' foi travado.
Acrescente um hook que bloqueia e indica uma saída
Um bloqueio sem alternativa ensina o agente a contorná-lo.
O .claude/settings.json completo, com uma chave hooks ao lado de permissions (guia de hooks(abre num novo separador)):
{
"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} aponta para a raiz do projeto, por isso o caminho continua válido depois de o agente mudar de pasta. Um caminho errado para o script faz o python3 terminar com 2, e todas as chamadas Bash ficam bloqueadas com um erro can't open file (não é possível abrir o ficheiro). Ou seja, falha pelo lado seguro, e o primeiro comando mostra-o logo.
E o 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())
Cada linha tem uma razão de ser:
- O código de saída 2 bloqueia a chamada; o agente lê o stderr.
(?<!-)\brm\bapanha qualquer tokenrm, incluindo/bin/rm. O lookbehind mantémdocker run --rmpermitido.- Uma entrada ilegível termina com 0: nunca bloqueia uma sessão, e passa sem verificação.
- Sem padrão para interpretadores. Os one-liners com
os.removeeshutil.rmtreepassam: o hook só bloqueia o que os seus padrões indicam.
Um hook corre em todas as chamadas Bash, seja o que for que tenham dito ao agente (graph engineering). É por isso que uma regra destas pertence a um hook e não a um CLAUDE.md que não para de crescer.
Teste o hook antes de confiar nele
Uma regex por testar é um palpite.
Passe um payload de exemplo ao script:
echo '{"tool_name":"Bash","tool_input":{"command":"find exports -mindepth 1 -delete"}}' \
| python3 .claude/hooks/delete-guard.py; echo "exit $?"
Acrescente um caso de teste por formato, incluindo as falhas esperadas:
| Comando | Esperado |
|---|---|
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, uma falha conhecida |
git push -f origin main | exit 2 |
grep -rn "rm -rf" scripts/ | exit 2, um bloqueio excessivo conhecido |
Termine com um bloqueio real: numa pasta de testes, peça ao Claude Code para apagar um ficheiro descartável.
Uma tentativa de eliminação, passo a passo
O melhor bloqueio é aquele de que o agente recupera sozinho.

- Pedido. A Marrowby Tiles pede: «Gere de novo a exportação do feed de produtos, comece do zero.» O agente executa
find exports -mindepth 1 -delete(marcador 1). - Bloqueio. O hook corresponde a
\bfind\b.*\s-delete\be termina com 2; a mensagem indica_archive/neste projeto (marcador 2). - Desvio. O agente executa
mkdir -p _archive && mv exports/* _archive/(marcador 3). - Resultado. 2 itens movidos, 0 eliminados:
_archive/product-feed.csve_archive/old/(marcador 4).
O que corre mal com uma proteção contra eliminações
Preferimos explicar um bloqueio indevido a restaurar uma pasta.

Quatro falhas que tivemos, cada uma com a sua regra:
- Uma pesquisa foi bloqueada. Um
grep -rn "rm -rf" scripts/só de leitura correspondeu ao padrão. Regra: manter o falso positivo (falhar pelo lado seguro) e o respetivo caso de teste. - As mensagens de commit ativavam o hook. «Never rm -rf exports» correspondia como se fosse um comando. Regra: retirar primeiro os argumentos
-me--messageentre aspas. - O caminho de arquivo saía do projeto. A nossa mensagem de bloqueio indicava uma pasta na raiz do nosso repositório, e um agente de teste noutro projeto moveu para lá os seus ficheiros. Regra: indicar
_archive/neste projeto. - Um comando git apagou trabalho. Um
git checkout --em duas pastas apagou trabalho sem commit. Regra: aplicar também a negação e o hook aos comandos git que eliminam.
Quando ninguém está a ver
Os pedidos de aprovação não são um controlo para execuções sem supervisão. Os pontos de aprovação e o isolamento são.
As nossas execuções sem supervisão ignoram os pedidos por conceção. Há três controlos que as protegem:
| Controlo | O que faz | O que não faz |
|---|---|---|
| Regras de negação e o hook | Bloqueiam os formatos de eliminação que indicam, nas execuções locais | Apanhar formatos que não indicam |
| Execução isolada | Corre cada tarefa no seu próprio ambiente, como marrowby-run, a partir de commits com push, para que uma eliminação não chegue à sua cópia | Travar um comando dentro da execução |
| Passo de aprovação antes do merge | Uma pessoa aprova antes de uma alteração, incluindo uma eliminação, chegar ao main | Travar um comando durante a execução |
Os nossos fluxos de trabalho param para uma aprovação onde está o risco (humano no circuito), e o nosso ciclo de reparação do site corre num ambiente isolado.
Copie hoje o bloco de negação e o hook. Depois, execute os seus trabalhos de agentes sem supervisão como tarefas do ConvOps(abre num novo separador), com um passo de aprovação antes do merge e uma execução isolada.
A mesma proteção no OpenCode e no Kimi Code CLI
Um formato de proteção, três ficheiros.
Testámos esta proteção em projetos de teste; a proteção que usamos no dia a dia corre no Claude Code.

OpenCode 1.18.31
No OpenCode, as regras de negação em permission.bash travaram rm e find -delete, e o agente apagou então com um one-liner em Python. Um plugin, .opencode/plugins/delete-guard.js, verifica cada comando bash em tool.execute.before, com os.remove e shutil.rmtree entre os seus padrões, e lança uma mensagem que indica _archive/. Bloqueou os seis formatos que experimentámos, e o agente arquivou em vez de apagar. opencode run --pure ignora os plugins, incluindo a proteção.
Kimi Code CLI 0.31.1
O Kimi Code CLI (que corre modelos como o Kimi K3) aceita um hook PreToolUse em ~/.kimi-code/config.toml:
[[hooks]]
event = "PreToolUse"
matcher = "Bash"
command = "node ~/.kimi-code/hooks/delete-guard.mjs"
O script termina com o código 2 e a mesma mensagem e, em modo yolo, bloqueou os seis formatos. Os hooks vivem apenas na configuração do utilizador, nunca no projeto. A regra nativa Bash(rm *) nunca correspondeu a um caminho com barra; Bash(rm */**) corresponde.
Seja qual for a ferramenta, a ordem é a mesma: push, negação, hook, teste.
Passos
Faça commit e push do seu trabalho
Execute git status, faça commit de tudo e push antes de iniciar o Claude Code. Corra o trabalho arriscado numa git worktree ou numa execução isolada que parte do remoto. Resultado: nada do que lhe importa existe apenas no seu disco.
Acrescente regras de negação ao Claude Code
Cole um bloco permissions.deny no .claude/settings.json do projeto que cubra rm, rmdir, git rm, git clean, git reset, git restore, git checkout num caminho, git stash drop e clear, e force push. Resultado: o Claude Code recusa esses comandos, mesmo com --dangerously-skip-permissions.
Acrescente um hook PreToolUse que indica o caminho seguro
Guarde delete-guard.py em .claude/hooks/ e ligue-o como hook PreToolUse com o matcher Bash. Resultado: um comando que corresponda termina com 2 e o agente lê uma mensagem que lhe diz para mover os ficheiros para _archive/ neste projeto.
Teste o hook com JSON de exemplo
Passe ao script um payload de exemplo para cada formato de eliminação e verifique o código de saída: 2 para eliminações, 0 para docker run --rm e para mensagens de commit. Depois provoque um bloqueio real numa pasta de testes. Resultado: uma lista de casos de teste que regista também a falha conhecida e o bloqueio excessivo conhecido.
Isole as execuções sem supervisão
Corra as tarefas agendadas de agentes num ambiente isolado que parte de commits com push, com um passo de aprovação antes do merge. Resultado: uma eliminação durante uma execução não chega nem à sua cópia nem ao main sem o sim de uma pessoa. Crie uma conta gratuita no ConvOps em https://my.convops.app/register para as correr como tarefas.
Perguntas frequentes
O que impede o Claude Code de apagar ficheiros?
Há quatro camadas que impedem o Claude Code de apagar ficheiros: um pedido de aprovação, uma regra de negação em .claude/settings.json, um hook PreToolUse que verifica cada chamada Bash e um caminho de volta feito de trabalho com commit e push e de execuções isoladas. Só as três últimas funcionam em execuções sem supervisão, que ignoram os pedidos. Uma regra de negação compara o texto do comando, por isso find com -delete e /bin/rm passam por ela, e um hook só bloqueia os padrões que indica.
Quem precisa de impedir o Claude Code de apagar ficheiros?
Os programadores e as equipas que usam o Claude Code em repositórios reais precisam de uma proteção contra eliminações, sobretudo quando as tarefas de agentes correm sem supervisão ou de forma agendada. O resultado é que a eliminação fica bloqueada ou o trabalho continua a existir no remoto. O ConvOps executa essas tarefas com uma execução isolada e um passo de aprovação antes do merge, para que uma eliminação feita durante uma execução não chegue nem à sua cópia local nem ao main sem o sim de uma pessoa.
O Claude apagou ficheiros?
Para saber se o Claude Code apagou ficheiros, procure nas chamadas Bash da sessão formatos de eliminação: rm, rmdir, find com -delete, git checkout num caminho, git reset --hard, git clean ou um one-liner python3 -c. Depois execute git status para ver que ficheiros versionados estão em falta. Um ficheiro removido por um comando Bash desaparece do disco, a não ser que tivesse commit (com push ou local) ou que um hook o tenha movido para uma pasta _archive/.
Como evitar que o Claude apague ficheiros?
Faça commit e push do seu trabalho antes de o Claude Code começar. Depois acrescente regras de negação para rm, rmdir e comandos git destrutivos em .claude/settings.json, acrescente um hook PreToolUse que termina com o código 2 e diz ao agente para mover os ficheiros para _archive/, e teste o hook passando-lhe JSON de exemplo. Por fim, corra as tarefas sem supervisão em execuções isoladas. As regras de negação só comparam o texto do comando, por isso o hook e o trabalho com push cobrem o que lhes escapa.
Porque é que o Claude apagou os meus projetos?
O Claude Code apaga ficheiros quando uma tarefa soa a recomeço: quando lhe pedem para começar do zero, esvazia primeiro a pasta, e no nosso teste escolheu find com -delete por iniciativa própria. Os comandos git que revertem trabalho, como git checkout num caminho, git reset --hard ou git clean, também apagam alterações sem commit. Sem uma regra de negação ou um hook, nada se interpõe entre o comando e o disco.
O Claude consegue recuperar ficheiros apagados?
O Claude Code só recupera o que existe fora dos ficheiros de trabalho: commits, locais ou com push (só os que têm push sobrevivem a uma pasta perdida ou apagada), e ficheiros que um hook desviou para uma pasta _archive/ em vez de os eliminar. O trabalho sem commit removido por uma eliminação Bash ou por um comando git que reverte perde-se de vez. As execuções isoladas do ConvOps partem de commits com push no seu próprio ambiente, por isso uma eliminação dentro de uma delas não chega à sua cópia local.
As regras de negação continuam a funcionar no modo bypassPermissions?
Sim. Nas nossas execuções de teste no Claude Code 2.1.295 com --dangerously-skip-permissions, a regra de negação Bash(rm *) bloqueou o rm, e um hook PreToolUse que termina com o código 2 também o bloqueou, o que coincide com a documentação dos modos de permissão da Anthropic. A limitação: a mesma regra de negação deixou que find com -delete, /bin/rm e um one-liner em Python apagassem o ficheiro, porque as regras de negação comparam o texto do comando, não o que o comando faz.
