Hooks do Claude Code não funcionam no Windows: causas e soluções
O mesmo hook que roda bem no Mac não faz nada no Windows ou, pior, bloqueia todos os comandos. Quase sempre o culpado é o shell. Veja como o Claude Code executa hooks no Windows e como deixar os seus confiáveis.
RESPOSTA CURTA
No Windows, o Claude Code executa o command de um hook com o Git Bash ou, quando o Git Bash não está instalado, com o PowerShell. Diga qual você quer com "shell": "powershell" ou "shell": "bash", mantenha o stdout com um único objeto JSON (sem echos do perfil), use o código de saída 2 para bloquear e atualize o Claude Code (vários bugs de hooks no Windows foram corrigidos em versões até a 2.1.310). Depure com /hooks e claude --debug.
Qual shell executa seu hook
O Claude Code passa a string de command para um shell: sh -c no macOS e no Linux, Git Bash no Windows ou PowerShell quando o Git Bash não está instalado. Essa única frase da referência de hooks explica a maioria dos relatos de “no meu Mac funciona”. Um comando escrito para PowerShell falha no Git Bash, um script bash falha no PowerShell, e qual dos dois você recebe depende de o Git for Windows estar instalado ou não.
Acabe com a adivinhação usando o campo shell. Ele aceita "bash" ou "powershell":
Se você quer o Git Bash e o Claude Code não o encontra, instale o Git for Windows ou defina CLAUDE_CODE_GIT_BASH_PATH com o local dele e inicie uma nova sessão.
Sintomas e suas causas
| O que você vê | Causa provável | Solução |
|---|---|---|
| O hook parece nunca rodar | Versão antiga do Claude Code, evento ou matcher errado, JSON inválido nas configurações | Atualize; confira /hooks; remova comentários e vírgulas sobrando no final |
ParserError ou “unexpected token” | Sintaxe de PowerShell chegando ao Git Bash, ou o contrário | Defina shell explicitamente |
| A saída do hook “does not start with {” (não começa com {) | Algo foi impresso antes do JSON, muitas vezes um perfil de shell | Silencie a saída do perfil; envie logs para o stderr |
Um caminho com \ e / misturados não é encontrado | Variável de caminho do Windows concatenada com um sufixo no estilo Unix | Use a forma exec: o programa em command e cada argumento em args |
powershell: Permission denied vindo do Git Bash | Relatado de forma intermitente na 2.1.283 e nas 2.1.304–2.1.305 (#103910, em aberto) | Use "shell": "powershell" para que o Git Bash não fique no meio |
| Um hook de proteção deixa tudo passar | Código de saída 1 em vez de 2, ou um bug de fail-open em versões antigas | Saia com 2 para bloquear; atualize para a 2.1.310 ou posterior |
| O hook retorna na hora, sem saída | Um programa do subsistema GUI pelo qual o shell não espera | Use um programa de console ou espere pelo processo |
Apenas JSON no stdout
O Claude Code lê a saída padrão do seu hook como resposta. A regra na documentação é curta: não imprima nada além do objeto JSON no stdout. No Windows isso quebra de um jeito traiçoeiro: o Git Bash e algumas configurações ainda carregam seu perfil de shell, e um echo incondicional nesse perfil é colocado antes do seu JSON. Aí o hook “funciona” no terminal e falha dentro do Claude Code.
Em vez disso, registre os logs em um arquivo ou no stderr e faça a saída do seu perfil depender de um shell interativo.
Códigos de saída: 2 bloqueia, 1 não
Sair com 0 é sucesso. Sair com 2 é um erro bloqueante: em eventos que podem ser bloqueados, o Claude Code interrompe a ação e mostra seu stderr. Sair com 1 sem JSON válido é um erro não bloqueante, e a ação segue em frente. Se o seu hook impõe uma política, use a saída 2.
Nem todo evento pode ser bloqueado assim. O código de saída de um hook Notification é ignorado. Um hook Stop que sai com 2 mantém o Claude trabalhando. Um hook PermissionRequest ignora completamente a saída 2. Ele decide por meio de um objeto decision no seu JSON.
O Claude Code 2.1.309 corrigiu hooks de PowerShell que não bloqueavam quando o programa do hook saía com o código 2. Se a sua proteção em PowerShell parece ignorada, confira primeiro a sua versão.
Correções para Windows que valem a atualização
O changelog da Anthropic tem uma longa lista de correções de hooks no Windows. As que explicam tópicos antigos de fórum:
| Versão | Correção |
|---|---|
| 2.1.47 | Hooks PreToolUse e PostToolUse falhavam silenciosamente no Windows; agora rodam com o Git Bash em vez do cmd.exe |
| 2.1.161 | Hooks que chamam o bash explicitamente (/usr/bin/bash script.sh) falhavam com “command not found” |
| 2.1.299 | Hooks PreToolUse e PermissionRequest sem shell definido deixavam tudo passar ao falhar (fail-open) sem o Git for Windows; uma proteção com falha agora bloqueia |
| 2.1.309 | Hooks de PowerShell não bloqueavam com o código de saída 2; proteções com "shell": "powershell" deixavam tudo passar ao falhar (fail-open) sem o Git for Windows |
| 2.1.310 | Proteções cujo comando era só strings entre aspas (como um caminho entre aspas) deixavam tudo passar ao falhar (fail-open) sem o Git Bash |
A issue mais antiga, intitulada “No hook is working on Windows” (#10450), foi relatada na 2.0.27; um comentarista descobriu que o stdin não chegava aos comandos dos hooks. Se você está em uma versão tão antiga, atualize antes de depurar qualquer outra coisa.
Chamando um programa diretamente
Se o seu hook é um programa compilado, você pode pular o shell com a forma exec (o campo args; shell é ignorado quando args está definido). No Windows, a forma exec exige que command aponte para um executável de verdade, como um .exe; shims .cmd e .bat (incluindo a maioria das coisas que o npm instala) não podem ser iniciados sem um shell. Aponte para o .exe ou use a forma shell.
Uma lição de quando criamos nosso próprio helper de hooks: no Windows, quando um shell inicia um programa compilado para o subsistema GUI, ele não espera pelo programa. O shell retorna na hora, o agente lê uma saída vazia e o hook parece morto. Programas de hook precisam ser programas de console.
Hooks de plugin e caminhos
Hooks de plugin que montam caminhos como ${CLAUDE_PLUGIN_ROOT}/hooks/run.sh quebraram no Windows entre a v2.1.2 e a v2.1.12 (#18610): a variável se expande para um caminho com barras invertidas, e adicionar um sufixo com barra normal gera um caminho misto que o bash não consegue resolver. A documentação atual traz a solução limpa: use a forma exec sempre que um hook fizer referência a um placeholder de caminho. Coloque o programa em command e cada argumento em args, por exemplo "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/format.js"]. Cada elemento é passado como um único argumento, sem shell para estragá-lo.
Checklist de depuração
/hooksabre uma lista somente leitura de todos os hooks configurados e de onde cada um vem.claude --debug(ou/debugno meio da sessão) mostra cada execução de hook, sua saída e seu código de saída.claude --debug-file <path>grava isso em um arquivo.- Valide o JSON. Os arquivos de configuração são JSON estrito; uma única vírgula sobrando no final desativa o arquivo inteiro.
- Confira o matcher. Matchers diferenciam maiúsculas de minúsculas;
"*",""ou nenhum matcher correspondem a tudo. - Execute o comando você mesmo no mesmo shell que o Claude Code vai usar, passando um JSON de exemplo pelo pipe.
- Olhe o timeout. Hooks de comando têm timeout padrão de 600 segundos; um hook que espera por uma pessoa precisa dessa folga.
Para desligar todos os hooks enquanto investiga, defina "disableAllHooks": true.
Se você só queria notificações e aprovações
Muitas configurações de hooks no Windows existem por um único motivo: saber quando o Claude Code precisa de você e responder. O Pet Island faz isso sem nenhum script. Ele se conecta ao Claude Code com um clique, cuida dos detalhes do Windows para você e coloca cada pedido de permissão, pergunta e plano em uma pequena ilha no topo da tela, com o comando ou o diff, onde você permite ou nega. Também funciona quando o Claude Code roda dentro do WSL.
PERGUNTAS FREQUENTES
Qual shell executa os hooks do Claude Code no Windows?
O Git Bash ou, quando o Git Bash não está instalado, o PowerShell. Defina "shell": "powershell" ou "shell": "bash" no hook para escolher explicitamente.
Por que meu hook do Claude Code funciona no terminal, mas não no Claude Code?
Normalmente é saída extra: um perfil de shell que imprime texto com echo faz esse texto aparecer antes do JSON do hook. Imprima apenas o objeto JSON no stdout.
Como faço um hook bloquear uma chamada de ferramenta?
Saia com o código 2 (ou retorne uma decisão deny em JSON). O código de saída 1 é um erro não bloqueante, e a ação segue em frente. Hooks PermissionRequest ignoram a saída 2 e usam um objeto decision no lugar.
O comando de um hook pode ser um arquivo .cmd ou .bat?
Não na forma exec. No Windows, ele precisa apontar para um executável de verdade, como um .exe. Use a forma shell para executar arquivos .cmd ou .bat.
Como vejo por que um hook falhou?
Execute claude --debug ou digite /debug e use /hooks para ver quais hooks estão carregados.
Fontes
Todas as configurações e comportamentos desta página foram conferidos com estas páginas em 26 de outubro de 2026. Os agentes mudam rápido; se algo não bater mais, vale o que diz a página oficial.
- Documentação do Claude Code: Hooks reference
- Documentação do Claude Code: Hooks guide
- Claude Code: CHANGELOG
- Issue #10450: nenhum hook funciona no Windows
- Issue #18610: hooks de plugin não conseguem executar scripts no Windows
- Issue #103910: hooks falham de forma intermitente ao iniciar o powershell (Git Bash)