Claude Code fez commit sem pedir? Como recuperar e como bloquear o Git na próxima tarefa

Claude Code commit sem permissão tem duas frentes: recuperar o trabalho e travar o Git antes da próxima tarefa. Para recuperar, use git reflog para achar onde o HEAD estava, git fsck --full para objetos órfãos e git stash list se o estado sujo foi empilhado. Para prevenir, tire a proibição do texto e coloque em configuração: regra deny em .claude/settings.local.json, hook PreToolUse saindo com código 2, modo Plan ou um worktree isolado. Instrução em prosa no CLAUDE.md orienta, mas não bloqueia nada.
Você pede um ajuste em dois arquivos, volta pro terminal, roda git log e tem um commit ali que você não escreveu
Ou pior: a branch é outra, e aquele trecho que você tinha ajustado na mão sumiu
A boa notícia é que quase sempre dá pra recuperar, porque o Git guarda muito mais coisa do que parece
A outra boa notícia é que dá pra prevenir de verdade, com configuração, não com pedido gentil no CLAUDE.md
Este post separa as duas coisas na ordem certa: primeiro salvar o que sumiu, depois travar o versionamento pra isso não acontecer de novo, com regra deny e com hook
Sintoma 1: o Claude Code criou um commit que você não pediu
O sintoma: o git log mostra um commit novo, e ele vem com trailer de atribuição do tipo Co-Authored-By
Esse trailer é o padrão do Claude Code nos commits que ele faz, e é controlado pela configuração attribution
Ela tem precedência sobre a configuração depreciada includeCoAuthoredBy, e pra remover a atribuição você define commit e pr como strings vazias
A causa: comandos Bash (categoria que inclui qualquer comando git) exigem aprovação por padrão no Claude Code, diferente de leitura de arquivo, que passa direto
Então como é que o commit rodou sozinho?
Provavelmente por causa de um "Yes, and don’t ask again" clicado no automático em algum momento
Se liga na diferença: em edição de arquivo, esse "não perguntar de novo" vale até o fim da sessão
Em comando Bash, a permissão vira PERMANENTE por diretório de projeto e por comando, gravada em .claude/settings.local.json na raiz do repo git
Formação Claude Code
Domine Claude Code do absoluto zero até o avançado
- 116 aulas
- 4 projetos
- 9h 23min
Ou seja: você liberou uma vez, e ficou liberado
A solução: antes de desfazer qualquer coisa, olha o que tem dentro do commit
git log -1 --stat
git show HEAD
Depois escolhe o modo de git reset conforme o que você precisa que SOBREVIVA:
--soft: deixa working tree e index inalterados, ou seja, desfaz o commit e mantém as mudanças preparadas--mixed(o padrão): deixa o working directory inalterado e atualiza o index, então nada fica em stage--keep: pensado justamente pra remover os últimos commits da branch mantendo as mudanças no working tree--hard: working tree, index e HEAD passam a apontar pro commit alvo
git reset --soft HEAD~1
Tome cuidado com o --hard aqui: ele é o único da lista que joga fora o que estava no working tree
Como prevenir: abre o .claude/settings.local.json do repositório e olha o que foi gravado na lista de allow
Se tiver comando git ali, tira e troca por deny (a sintaxe certa está algumas seções abaixo)
Sintoma 2: as suas mudanças sumiram depois de um reset ou de um checkout
O sintoma: os arquivos voltaram pra um estado anterior, e o editor não tem mais nada pra desfazer
A causa: o reset --hard move working tree, index e HEAD pro commit alvo de uma vez só
E aqui vem a parte que dói: trabalho que nunca foi commitado não aparece no reflog, porque reflog registra movimento de referência, não rascunho solto
A solução: comece pelo reflog
Os reflogs registram quando as pontas de branches e outras referências foram atualizadas no repositório local, e deixam você referenciar valores antigos, tipo HEAD@{2} pra dizer "onde o HEAD estava dois movimentos atrás"
git reflog
Achou o ponto certo? Pega o SHA-1 do commit e cria uma branch apontando pra ele
git branch recupera-trabalho <sha-1>
Se o reflog não cobrir o seu caso, o próximo passo é procurar objeto órfão, aquele que não é apontado por nenhum outro objeto
git fsck --full
O git fsck verifica a integridade do banco de objetos e mostra esses pendurados, e o capítulo de manutenção e recuperação do Git descreve o caminho completo
E tem o terceiro cenário: o agente empilhou o estado sujo em vez de descartar
git stash list
git stash apply
O stash guarda o estado sujo do working directory numa pilha que pode ser reaplicada, inclusive em outra branch
Porém, atenção: entrada de stash descartada ou limpa NÃO é recuperável pelos mecanismos normais de segurança
Como prevenir: commitar ou stashear antes de soltar uma tarefa grande pro agente
É chato, é manual, e é o que salva você do parágrafo anterior 🙂
Sintoma 3: o /rewind não traz o seu código de volta
O sintoma: você aperta Esc duas vezes com o campo de entrada vazio (ou roda /rewind), e as opções Restore code e Restore code and conversation simplesmente não aparecem
Ou aparecem, você restaura, e o estado continua errado
A causa: as duas opções de código só aparecem quando o checkpoint selecionado tem mudanças de arquivo rastreadas
E o checkpointing tem um limite bem claro do que ele rastreia
Não entra: alteração feita por comando bash (tipo rm, mv, cp), processo externo, ação em sistema remoto e edição feita manualmente fora do Claude Code
Tem mais um detalhe que pega gente: na restauração, caminho rastreado que seja symlink ou hard link é pulado, com aviso na tela
A solução: quando só o contexto interessa, usa Restore conversation e resolve o código pelo Git mesmo
O menu ainda tem Restore conversation, as opções de summarize e o Never mind, então dá pra escolher o que volta
Como prevenir: trata o checkpoint como desfazer de edição, não como histórico
A própria documentação orienta continuar usando controle de versão pra commits, branches e histórico de longo prazo
E checkpoints somem junto com as sessões depois de 30 dias, período ajustável na configuração cleanupPeriodDays
Sintoma 4: você escreveu no CLAUDE.md pra não mexer no Git e ele mexeu
O sintoma: tem lá, em bom português (ou inglês), a instrução proibindo commit, push e troca de branch sem autorização
E o agente executa mesmo assim
A causa: instrução em prosa é orientação, não é trava
O CLAUDE.md fala com o modelo, e a camada de permissão fala com o sistema, são coisas diferentes
E não é só você: existe relato público aberto no repositório do Claude Code exatamente sobre isso, a issue #40695, de março de 2026, sobre operações git commit, push e branch executadas apesar de regras explícitas no CLAUDE.md proibindo sem permissão do usuário
Tem também pedido aberto de um modo de permissão específico pra git no ciclo do Shift+Tab, a issue #34429, também de março de 2026, cobrindo git add, git commit, git push, git reset --hard, git rebase e git stash pop
A solução: mover a proibição do texto pra configuração
É o assunto das próximas duas seções, a regra de permissão e o hook, e é a parte que realmente muda o comportamento
Como bloquear comandos git no Claude Code pelas regras de permissão
Antes do como, o porquê: a regra de permissão é avaliada pelo sistema antes do comando rodar, então ela não depende de o modelo "lembrar" do combinado
- Escolha o escopo de arquivo certo. São três:
~/.claude/settings.json(user),.claude/settings.json(project) e.claude/settings.local.json(local)
A precedência, do maior pro menor, é: managed settings, argumentos de linha de comando, flag --settings, local, projeto, usuário
E se liga nisso, que é o ponto que confunde: chave de lista definida em mais de um arquivo é COMBINADA, não substituída
O erro comum deste passo: colocar a regra no arquivo do projeto achando que ela apaga o que já está no local, quando na verdade as duas listas se somam
- Escreva a regra
denycom o padrão certo. Regra de Bash casa com o texto INTEIRO do comando, usando*como curinga
E a forma : só é reconhecida no fim do padrão: Bash(ls:) equivale a Bash(ls ), enquanto Bash(git: push) trata o dois-pontos como caractere literal e simplesmente não casa com nada
O erro comum deste passo: escrever Bash(git:* commit), ver o JSON salvo bonitinho e achar que está protegido
- Confira a ordem de avaliação. É
deny, depoisask, depoisallow, e a especificidade da regra não muda essa ordem
Um deny amplo como Bash(aws *) bloqueia mesmo existindo um allow mais específico como Bash(aws s3 ls), e o mesmo vale entre ask e allow
E qual é o papel do allow nessa fila, então? Ele fica por último justamente porque só é considerado quando nenhum deny e nenhum ask casaram com o comando
Ou seja: allow serve pra liberar o que ainda está livre, nunca pra destravar o que um deny já barrou
O erro comum deste passo: supor que o allow mais específico vence o deny amplo, quando não vence
{
"permissions": {
"deny": [
"Bash(git commit *)",
"Bash(git push *)",
"Bash(git reset *)",
"Bash(git checkout *)"
]
}
}
Repara que cada padrão cobre um comando: como a regra casa com o texto inteiro, vale escrever uma linha por comando que você quer fora do escopo do agente
A mesma lógica de padrão serve pra impedir o Claude Code de editar arquivos que você não pediu, é a mesma camada de permissão, só muda a ferramenta que você está mirando
E se você quer uma trava que atua ANTES dessa ordem de avaliação toda, o caminho é hook, que é exatamente a próxima seção
Trava dura: hook PreToolUse que barra o comando antes da permissão
Regra deny resolve pra maioria dos casos, beleza?
Mas se você quer garantia acima da regra de permissão, o caminho é hook
- Configure o evento
PreToolUseno settings.json, com matcher por nome de ferramenta e umcommandapontando pra um script no projeto
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-git.sh"
}
]
}
]
}
}
O erro comum deste passo: escrever o matcher com a caixa errada, tipo bash em minúscula, e achar que o hook está ativo
O matcher diferencia maiúsculas de minúsculas, então ele simplesmente não casa e o comando passa liso
- Leia o JSON que chega no stdin. O hook recebe, entre outras coisas,
tool_name,tool_input.command,cwdepermission_mode
É com o tool_input.command que você decide se aquele comando específico entra ou não
- Saia com código 2 pra bloquear, escrevendo no stderr o motivo
Esse texto do stderr é o que o Claude enxerga como recusa, então escreva uma frase que oriente ele ("o versionamento é manual neste repo") em vez de um erro seco
#!/usr/bin/env bash
input=$(cat)
cmd=$(printf '%s' "$input" | jq -r '.tool_input.command // ""')
case "$cmd" in
*"git commit"*|*"git push"*|*"git reset"*|*"git checkout"*)
echo "Versionamento e manual neste repositorio: nao rode comandos git, so altere os arquivos" >&2
exit 2
;;
esac
exit 0
O que faz esse hook ser a trava mais dura: o exit code 2 segue o MESMO caminho do deny, e bloqueia a chamada mesmo que exista uma regra allow correspondente
A precedência entre hooks PreToolUse também é deny > defer > ask > allow
E se você não quiser escrever a filtragem na mão, existe o campo if, que filtra por nome de ferramenta e argumentos juntos usando a sintaxe das regras de permissão
Regra deny, hook, modo Plan ou worktree: qual camada usar
São quatro camadas diferentes, e elas não competem: dá pra empilhar
| Camada | O que ela resolve | Ponto de atenção |
|---|---|---|
Regra deny no settings |
Bloqueia o comando pela avaliação de permissão, que roda antes da execução | Só funciona se o padrão estiver escrito certo (Bash(git:* commit) não casa) |
Hook PreToolUse com exit 2 |
Bloqueia antes das regras de permissão serem avaliadas | Vale mesmo existindo allow; matcher diferencia maiúscula de minúscula |
| Modo Plan | O Claude pesquisa e propõe, lê arquivos e roda comandos de exploração, sem editar o código-fonte até você aprovar o plano | Depende de você revisar o plano antes de aprovar |
Git worktree (--worktree ou -w) |
Isola o que o agente faz do seu checkout principal | Worktree criado em .claude/worktrees/<nome>/ na raiz do repo, em branch nova worktree-<nome> |
Pra subagent, dá pra deixar o isolamento permanente com isolation: worktree no frontmatter
E um alerta que vale por toda a tabela: o modo bypassPermissions desativa os prompts de permissão E as verificações de segurança, com tool calls executando imediatamente, incluindo escrita em caminho protegido
A documentação restringe o uso dele a container, VM ou dev container sem acesso à internet
Não é modo de trabalho no seu repo do dia a dia, beleza?
Como escrever a tarefa pra deixar o versionamento fora do escopo
A configuração é a trava
A instrução é o que evita o atrito de ficar batendo na trava o tempo todo
Os dois juntos funcionam melhor que qualquer um sozinho
Refatoração longa em repo compartilhado:
Combinação: worktree isolado (-w com um nome) mais deny em Bash(git push *)
Assim o agente trabalha numa branch worktree-<nome> que não é a sua, e nada sai da máquina
A instrução deve pedir a mudança de código e declarar explicitamente que commit, branch e push ficam com você
Tarefa exploratória em código que você ainda não entende:
Combinação: modo Plan primeiro, aprovação depois
Esse é o cenário clássico de tarefa vaga, tipo pedir ganho de performance sem métrica nenhuma na mão
Como no Plan ele lê arquivos e roda comandos de exploração sem editar o código-fonte, você vê o plano antes de qualquer coisa acontecer
A instrução deve pedir o diagnóstico e o plano, e dizer que a decisão de commit é sua depois da leitura
Repo com deploy automático a partir da branch:
Combinação: hook PreToolUse como trava
Aqui o argumento não é conforto, é consequência: um push acidental vira deploy
O hook é a camada que não depende de o agente respeitar texto, porque ele barra a chamada antes
A instrução deve dizer que a tarefa termina no arquivo alterado, e que publicação é um passo humano separado
Conclusão
Recuperar é quase sempre possível enquanto o reflog e os objetos ainda existirem, e agora você tem os três caminhos na mão: git reflog, git fsck --full e git stash list
O que muda o jogo mesmo é sair da instrução em texto e ir pra configuração, com regra deny bem escrita e, quando o risco pede, um hook PreToolUse por cima
Prosa no CLAUDE.md orienta, mas não bloqueia
Próximo passo concreto, e leva dois minutos: abre o .claude/settings.local.json do repo que você está agora
Olha quais permissões de Bash foram gravadas ali por algum "não perguntar de novo" antigo
E decide, uma por uma, quais delas viram deny hoje 😀
até o próximo post!
Perguntas frequentes
Qual a sintaxe certa pra bloquear comando git específico no Claude Code?
A regra vale pro texto inteiro do comando, com como curinga, por exemplo deny com Bash(git push ) e allow com Bash(git commit ) na lista de permissões. Repara que Bash(ls:) equivale a Bash(ls ), mas a forma : só é reconhecida no fim do padrão. Por isso Bash(git:* push) não funciona: o dois-pontos no meio vira caractere literal e não casa com nada
Uma regra allow mais específica vence um deny mais genérico?
Não. A ordem de avaliação é sempre deny, depois ask, depois allow, e isso independe de qual regra é mais específica. Um deny amplo tipo Bash(aws *) bloqueia mesmo existindo um allow mais fechado tipo Bash(aws s3 ls), e o mesmo vale na disputa entre ask e allow. O allow só é considerado quando nenhum deny e nenhum ask casaram com o comando
Dá pra travar comando git com hook em vez de só regra de permissão?
Dá, e é mais forte: um hook PreToolUse com matcher "Bash" que sai com código 2 bloqueia a chamada antes mesmo das regras de permissão serem avaliadas. A mensagem que o script manda pro stderr vira o motivo da recusa que o Claude enxerga. Na precedência entre hooks, deny vem antes de defer, ask e allow
Rodar em modo bypassPermissions evita ficar respondendo prompt de git?
Evita, mas ao custo de desativar os prompts de permissão e as verificações de segurança por completo, não só pra git. A documentação recomenda esse modo apenas em ambiente isolado, como container, VM ou dev container sem acesso à internet
Usar git worktree no Claude Code protege meu checkout principal de commit indesejado?
Ajuda bastante: com a flag –worktree (ou -w) seguida de um nome, o Claude Code cria um checkout separado em .claude/worktrees/<nome>/ na raiz do repositório, numa branch nova chamada worktree-<nome>. Qualquer commit que o agente fizer ali fica isolado do seu branch principal
Já existe um modo de permissão pronto só pra operações git no Claude Code?
Ainda não de forma nativa. Existe um pedido público em aberto, a issue #34429, propondo em março de 2026 um modo git-aware no ciclo do Shift+Tab pra bloquear ou perguntar em comandos como git add, git commit, git push, git reset –hard, git rebase e git stash pop. Até isso existir, a combinação de deny/allow ou um hook PreToolUse é o caminho
Formações
Formação Vibe Coding
Do Prompt ao Produto: Crie Software Real com IA
- 474 aulas
- 20 projetos
- 39h 27min
Blog | Mais populares
Bateu o limite de uso do Claude Code? Como retomar a tarefa sem refazer tudo
Bateu o limite de uso do Claude Code? Veja como retomar a tarefa de onde parou com /usage, CLAUDE.md e --continue, sem refazer nada.
Como pagar o Claude Code no Brasil: cartão, dólar, IOF e quanto fica em reais
Claude Code preço Brasil na prática: câmbio, IOF de 3,5% e quanto fica na fatura. Planos Pro e Max convertidos em reais e como pagar com cartão.
Como instalar uma skill no Claude Code: passo a passo
Saiba como instalar skill no Claude Code: use a pasta pessoal para todas as sessões ou a pasta de projeto para versionar. Frontmatter YAML é obrigatório.
