PermissionDenied Hook: Seu Auto Mode Bloqueia Ações e Você Nem Sabe?
Imagina o seguinte cenário: você configurou o Claude Code em auto mode pra rodar uma refatoração longa enquanto faz uma call. Quando volta, o agente está parado. Nada aconteceu nos últimos 40 minutos. O classifier do auto mode bloqueou uma ação que parecia arriscada — mas era perfeitamente segura — e o Claude ficou ali, tentando, sendo bloqueado de novo, até atingir o limite e pausar.
Na minha experiência com 300+ software houses, essa é a reclamação número um de quem adota auto mode: “funciona lindo até parar sem avisar.”
A Anthropic acabou de lançar na versão 2.1.88 um hook que resolve exatamente isso: o PermissionDenied. E se você usa auto mode em qualquer capacidade — seja interativo, pipeline CI/CD ou agent teams — precisa entender o que ele faz.
O que é o PermissionDenied Hook
O PermissionDenied é um novo evento de hook que dispara imediatamente após o classifier do auto mode negar uma tool call. Ele não impede o bloqueio — o classifier já decidiu. Mas ele te dá a chance de reagir: logar o evento, notificar alguém, e até permitir que o modelo tente novamente retornando {retry: true}.
Pra entender o valor, precisa entender o problema que ele resolve.
Como o auto mode decide
O auto mode usa o Claude Sonnet 4.6 como classifier separado. Não é o mesmo modelo que está trabalhando no seu código — é uma instância dedicada que avalia cada tool call antes de executar. Funciona em dois estágios:
1. Stage 1 — Fast filter: decisão binária (bloquear ou permitir) em um único token. Rápido.
2. Stage 2 — Chain-of-thought: se o Stage 1 sinaliza risco, o classifier faz raciocínio profundo antes de decidir.
Detalhe crucial: o classifier não vê a resposta do Claude nem os resultados de ferramentas anteriores. Ele vê apenas as mensagens do usuário e as tool calls. Isso é uma proteção contra prompt injection — conteúdo malicioso dentro de arquivos ou páginas web não consegue manipular a decisão do classifier.
As estatísticas reais
A Anthropic publicou os números do classifier com transparência rara:
| Métrica | Tráfego real (n=10k) | Ações overeager reais (n=52) | Exfiltração sintética (n=1k) |
|---|---|---|---|
| — | — | — | — |
| **Stage 1 FPR** | 8,5% | 6,6% FNR | 1,8% FNR |
| **Pipeline completo** | **0,4% FPR** | **17% FNR** | **5,7% FNR** |
Traduzindo: a cada 250 tool calls legítimas, 1 é bloqueada por engano (0,4% de falsos positivos). Parece pouco? Faça a conta numa sessão longa com agent teams rodando 500+ tool calls. São 2 bloqueios falsos. E cada um desses bloqueios pode cascatear.
O efeito cascata sem o hook
Sem o PermissionDenied hook, quando o classifier bloqueia uma ação:
1. O Claude recebe uma mensagem: “ação negada, encontre um caminho alternativo”
2. O Claude tenta outra abordagem — que muitas vezes é bloqueada também (mesmo falso positivo, padrão similar)
3. 3 bloqueios consecutivos OU 20 bloqueios totais na sessão → auto mode pausa completamente
4. O Claude Code volta a pedir permissão manual pra cada ação
5. Em modo não-interativo (-p): a sessão é abortada
Esse é o cenário silencioso que destrói pipelines de CI/CD. O agente trava, a pipeline fica hanging, e só descobre quando o timeout mata o processo.
Como funciona na prática
Configuração básica
O PermissionDenied hook se configura como qualquer outro hook no settings.json:
{
"hooks": {
"PermissionDenied": [
{
"hooks": [
{
"type": "command",
"command": "echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PermissionDenied\",\"retry\":true}}'"
}
]
}
]
}
}
Esse é o caso mais simples: sempre permite retry. Quando o classifier bloqueia qualquer ação, o hook retorna {retry: true} e o modelo tenta novamente. Útil pra ambientes onde você confia no escopo do trabalho e quer que falsos positivos não interrompam a execução.
Retry seletivo com logging
Na prática, você não quer retry cego. Você quer logar e decidir:
#!/bin/bash
# .claude/hooks/handle-denial.sh
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
REASON=$(echo "$INPUT" | jq -r '.denial_reason // "unknown"')
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
# Loga pra auditoria
echo "{\"timestamp\":\"$TIMESTAMP\",\"tool\":\"$TOOL_NAME\",\"reason\":\"$REASON\"}" >> ~/.claude/denial-audit.log
# Retry seletivo: permite retry pra git e npm, mas não pra Bash genérico
case "$TOOL_NAME" in
"Bash")
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
if echo "$COMMAND" | grep -qE "^(git|npm|yarn|pnpm) "; then
echo '{"hookSpecificOutput":{"hookEventName":"PermissionDenied","retry":true}}'
else
echo '{"hookSpecificOutput":{"hookEventName":"PermissionDenied","retry":false}}'
fi
;;
*)
echo '{"hookSpecificOutput":{"hookEventName":"PermissionDenied","retry":true}}'
;;
esac
{
"hooks": {
"PermissionDenied": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/handle-denial.sh"
}
]
}
]
}
}
Notificação via Slack/webhook
Pra quem roda pipelines autônomas, a combinação com HTTP hooks é natural:
{
"hooks": {
"PermissionDenied": [
{
"hooks": [
{
"type": "http",
"url": "https://hooks.slack.com/services/T00/B00/xxx",
"headers": { "Content-Type": "application/json" }
}
]
}
]
}
}
O endpoint Slack recebe o JSON do evento com tool_name, denial_reason e pode postar no canal da equipe: *”⚠️ Auto mode bloqueou git push na sessão abc123 — verificar.”*
Por que isso importa para sua Software House
O problema real: auto mode em produção
Toda software house que escala automação com IA enfrenta o mesmo dilema:
O PermissionDenied hook resolve o “mas” do auto mode. Ele transforma falsos positivos de interrupção silenciosa em eventos observáveis e acionáveis.
Custo operacional de denials não-tratados
Faz a conta:
Se sua SH roda 10 pipelines por dia com auto mode, são potencialmente 20 falsos positivos por dia. Sem observabilidade, você só descobre quando algo quebra.
Completando o ecossistema de hooks
Se você acompanha esta série, o PermissionDenied fecha um gap importante:
| Hook | O que monitora | Artigo |
|---|---|---|
| — | — | — |
| [HTTP Hooks](https://thuliobittencourt.com/claude-code-http-hooks-controle-centralizado-software-house/) | Todas as tool calls via webhook | v2.1.63 |
| [StopFailure](https://thuliobittencourt.com/claude-code-stopfailure-hook-alarme-automacao-ia/) | Erros de API (rate limit, auth) | v2.1.78 |
| [Conditional `if`](https://thuliobittencourt.com/claude-code-conditional-hooks-if-performance-software-house/) | Filtragem cirúrgica por args | v2.1.85 |
| [CwdChanged/FileChanged](https://thuliobittencourt.com/claude-code-cwdchanged-filechanged-hooks-environment-reativo-software-house/) | Mudança de diretório e arquivos | v2.1.83 |
| **PermissionDenied** | **Bloqueios do auto mode classifier** | **v2.1.88** |
Juntos, esses hooks formam uma camada de observabilidade completa sobre o Claude Code. Você sabe quando uma tool call é executada (PostToolUse), quando falha (PostToolUseFailure/StopFailure), quando o environment muda (CwdChanged/FileChanged), e agora quando o auto mode bloqueia algo (PermissionDenied).
Detalhe técnico: o classifier por dentro
Pra quem quer entender a fundo — porque na minha experiência, donos de SH que entendem a mecânica tomam decisões melhores sobre quando confiar e quando não confiar.
O classifier do auto mode tem uma arquitetura fixa com três slots customizáveis:
1. Infraestrutura confiável: repos, buckets, serviços internos que o classifier deve considerar seguros
2. Categorias de bloqueio: ações que sempre devem ser barradas
3. Exceções: overrides pra casos específicos
As 4 categorias padrão de bloqueio:
E aqui está o ponto-chave: o classifier não conhece a infra da sua empresa por padrão. Seus repos internos, buckets S3, serviços — tudo é “externo não-confiável” até você configurar. É por isso que muitos false positives acontecem em SHs: o classifier bloqueia push pro repo da empresa porque não sabe que é confiável.
A solução: configurar autoMode.environment nos managed settings. Mas mesmo com isso, false positives vão acontecer. E é aí que o PermissionDenied hook salva.
O que eu penso
Eu venho acompanhando a evolução dos hooks do Claude Code desde a v2.1.63. Cada release adiciona uma peça no quebra-cabeça da observabilidade. O PermissionDenied era a peça que faltava pra fechar o ciclo do auto mode.
Minha provocação: se você está usando auto mode sem nenhum hook configurado, você está voando cego. É como rodar um servidor em produção sem monitoring. Funciona 99,6% do tempo — mas os 0,4% que não funcionam são exatamente os que precisam de atenção.
O PermissionDenied hook não é sexy. Não é uma feature que vai pra demo no pitch de vendas. Mas é a diferença entre uma automação que funciona e uma automação que funciona e você sabe quando não funciona.
E na minha experiência mentoringando 300+ software houses: saber quando algo não funciona é mais valioso do que fazer funcionar.
Conclusão
O PermissionDenied hook transforma denials silenciosos do auto mode em eventos que você pode interceptar, logar e reagir. Com {retry: true}, falsos positivos viram inconveniências de 1 retry em vez de sessões abortadas. Com logging, você tem auditoria completa de tudo que o classifier bloqueia. Com webhooks, sua equipe é notificada em tempo real.
Se você usa auto mode — ou planeja usar — configure esse hook antes de colocar em pipeline de produção. É 5 minutos de setup que podem economizar horas de diagnóstico.
Se você quer implementar esse nível de automação com IA na sua software house, vem pro nosso próximo evento presencial. É onde a gente destrincha isso na prática, com código rodando ao vivo.
Sou Thulio, mentoro 300+ SHs desde 2016.




