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

recuperação de commit feito pelo Claude Code sem permissão no Git
Resposta rápida

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
Formação Recomendada

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

  1. 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

  1. Escreva a regra deny com 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

  1. Confira a ordem de avaliação. É deny, depois ask, depois allow, 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

  1. Configure o evento PreToolUse no settings.json, com matcher por nome de ferramenta e um command apontando 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

  1. Leia o JSON que chega no stdin. O hook recebe, entre outras coisas, tool_name, tool_input.command, cwd e permission_mode

É com o tool_input.command que você decide se aquele comando específico entra ou não

  1. 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



Escrito por | Matheus Battisti

Matheus Battisti
Fundador da Hora de Codar

Programador apaixonado pelo mundo das tecnologias, sempre buscando em aprender e se aprofundar em linguagens, frameworks e o que mais for necessário para executar um bom trabalho. Agora tem uma nova missão que é de passar seu conhecimento adiante para formar novos programadores e especializar mais os que já são.

Subscribe
Notify of
guest

0 Comentários
Oldest
Newest Most Voted

Formações

Formação Vibe Coding

Formação Vibe Coding

Do Prompt ao Produto: Crie Software Real com IA

  • 474 aulas
  • 20 projetos
  • 39h 27min

Blog | Mais populares