Home / Claude Code / Seu Pipeline CI/CD Trava Quando o Claude Code Nega um Comando Seguro? Um Hook Resolve

Seu Pipeline CI/CD Trava Quando o Claude Code Nega um Comando Seguro? Um Hook Resolve

Semana passada um dos meus mentorados me mandou uma mensagem às 23h: “Thulio, meu pipeline de deploy trava toda noite. O Claude Code roda em auto mode e nega o git push pro branch de staging. Toda manhã um dev tem que ir lá, ver o que aconteceu, e rodar de novo.”

Eu perguntei: “Quantas vezes por semana?”

“Umas 3-4. Às vezes mais.”

Esse é o tipo de problema silencioso que mata produtividade em software house. Não é um bug. Não é um erro de configuração. É o classifier do auto mode fazendo exatamente o que foi treinado para fazer — errar para o lado da cautela. E em ambientes headless como CI/CD, cautela demais significa pipeline morto.

A versão 2.1.89 do Claude Code trouxe uma feature que resolve isso: o PermissionDenied hook com suporte a retry. E a maioria das SHs nem sabe que existe.

O Que É o Auto Mode e Por Que Ele Nega Comandos Seguros

Se você usa Claude Code com 20 devs, provavelmente já ouviu reclamações sobre o auto mode. “Ele fica pedindo permissão pra tudo.” Ou pior: “Em CI/CD ele simplesmente para.”

O auto mode usa um classifier de dois estágios desenvolvido pela Anthropic para decidir se uma ação é segura sem perguntar ao humano:

Stage 1 — Fast Filter: decisão de um token (sim/não), sem raciocínio. Calibrado para “errar para o lado do bloqueio”. Taxa de falso positivo: 8.5% no tráfego real.

Stage 2 — Reasoning Review: só ativa quando o Stage 1 flagga. Aplica chain-of-thought para decisão mais nuanced. Reduz o falso positivo final para 0.4% no pipeline completo.

Parece ótimo no papel. 0.4% de falso positivo é excelente. Mas tem um detalhe que a maioria ignora: o Stage 1 nega 8.5% antes do Stage 2 ter chance de corrigir. E em certos cenários — especialmente comandos compostos como npm test && git push — o classifier é conservador por design.

A Anthropic admite no próprio blog de engenharia: o classifier recebe “apenas mensagens do usuário e tool calls do agente; removemos as mensagens do próprio Claude e outputs de ferramentas.” Isso previne o agente de persuadir o classifier com racionalizações, mas também significa que contexto legítimo é invisível para a decisão.

Resultado? Comandos perfeitamente seguros — que você autorizaria sem pensar — são negados. E quando isso acontece em modo interativo, aparece um diálogo e você aperta r pra retry. Inconveniente, mas funciona.

Em headless mode (claude -p)? O processo simplesmente termina.

3 negações consecutivas ou 20 totais na sessão = escalation. Em modo interativo, escala pro humano. Em headless, mata o processo. Seu pipeline morreu por um falso positivo.

O PermissionDenied Hook: Seu Pipeline Se Cura Sozinho

A v2.1.89 adicionou o hook PermissionDenied — um evento que dispara apenas em auto mode quando o classifier nega uma tool call. O hook recebe todos os detalhes da negação e pode retornar {retry: true} para dizer ao modelo que ele pode tentar de novo.

Veja o que o hook recebe como input:

{
  "session_id": "abc123",
  "hook_event_name": "PermissionDenied",
  "tool_name": "Bash",
  "tool_input": {
    "command": "git push origin staging",
    "description": "Push changes to staging"
  },
  "tool_use_id": "toolu_01ABC123...",
  "reason": "Auto mode denied: command targets a remote resource"
}

E a resposta que permite retry:

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionDenied",
    "retry": true
  }
}

Importante: o hook não reverte a negação. Ele adiciona uma mensagem à conversa dizendo ao modelo que pode tentar de novo. A diferença é sutil mas fundamental — o modelo recebe feedback e pode reformular a abordagem, e o Stage 2 do classifier terá outra chance de avaliar.

Aqui está um script real que você pode usar hoje:

#!/bin/bash
# .claude/hooks/retry-safe-commands.sh
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')
REASON=$(echo "$INPUT" | jq -r '.reason')
TOOL=$(echo "$INPUT" | jq -r '.tool_name')

# Log TODA negação para auditoria
echo "[$(date -Iseconds)] DENIED tool=$TOOL command=$COMMAND reason=$REASON" \
  >> "$CLAUDE_PROJECT_DIR/.claude/denied-commands.log"

# Permitir retry para comandos conhecidos e seguros
SAFE_PATTERNS=(
  "^git push"
  "^git checkout"
  "^npm test"
  "^npm run build"
  "^npm run lint"
  "^npx prettier"
  "^npx eslint"
  "^yarn test"
  "^bun test"
)

for pattern in "${SAFE_PATTERNS[@]}"; do
  if echo "$COMMAND" | grep -qE "$pattern"; then
    jq -n '{
      hookSpecificOutput: {
        hookEventName: "PermissionDenied",
        retry: true
      }
    }'
    exit 0
  fi
done

# Não é comando seguro conhecido — negação mantida
exit 0

Configuração no settings.json:

{
  "hooks": {
    "PermissionDenied": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/retry-safe-commands.sh"
          }
        ]
      }
    ]
  }
}

Pronto. Seu pipeline agora:

  1. Loga toda negação — compliance e auditoria resolvidos
  2. Permite retry para comandos segurosgit push, npm test, linters
  3. Mantém a negação para comandos desconhecidos — segurança intacta

Os Números Que Sua SH Deveria Conhecer

Vamos fazer a conta. 20 devs em auto mode, cada um fazendo ~100 tool calls por dia (dado real — sessions de coding agent fazem centenas de chamadas por sessão).

  • Stage 1 false positive rate: 8.5% (Anthropic, n=10.000)
  • Total tool calls/dia: 20 devs × 100 = 2.000
  • Negações por falso positivo/dia: ~170 (8.5% de 2.000)
  • Após Stage 2 corrigir: ~8 negações por falso positivo/dia (0.4% pipeline completo)

8 falsos positivos por dia parece pouco. Mas cada um interrompe o dev, que precisa:

  1. Ler o motivo da negação
  2. Decidir se é seguro
  3. Apertar r para retry ou reformular o comando

Tempo médio por interrupção: 2-3 minutos (context switch incluído). 8 × 2.5min = 20 minutos/dia de 20 devs perdidos em falsos positivos. 400 minutos/dia. Quase 7 horas.

Em CI/CD headless? Pior. Cada falso positivo mata o pipeline inteiro. Deploy noturno? Morreu. Build de staging? Morreu. Testes automatizados? Morreram. E ninguém descobre até a manhã seguinte.

O dado mais assustador vem do mercado mais amplo de AI agents:

  • 88% das organizações reportaram incidentes de segurança confirmados ou suspeitos com AI agents no último ano (Gravitee 2026)
  • Apenas 14.4% têm aprovação completa de segurança para todos os agents em produção
  • 50%+ dos agents operam sem supervisão de segurança ou logging
  • 46% de todos alertas de segurança são falsos positivos (Microsoft/Omdia 2026)
  • 73% dos times de segurança citam falsos positivos como principal desafio (SANS 2025)

A Anthropic projetou o auto mode para ser conservador — e fez certo. Mas conservadorismo sem válvula de escape é paralisação.

Por Que Isso Importa Mais em CI/CD do Que em Dev Local

Em dev local, o falso positivo é um incômodo. O dev vê o diálogo, aperta r, e segue. Irritante, mas não catastrófico.

Em CI/CD com claude -p (headless mode), a situação é diferente:

  1. Não tem humano para aprovar — o processo termina
  2. 3 negações consecutivas = hard stop — e no headless, hard stop = exit code não-zero
  3. Pipeline inteiro falha — não só o step do Claude Code, mas tudo que depende dele
  4. Ninguém descobre até investigar — o log diz “permission denied”, que pode ser confundido com erro de credencial

Sem o PermissionDenied hook, sua única opção é --dangerously-skip-permissions — que elimina TODA segurança — ou montar uma lista exaustiva de --allowedTools e torcer para cobrir todos os cenários.

O hook é o meio-termo: segurança ativa com escape inteligente.

Um caso real documentado pela API Stronghold: um agent de DevOps com credenciais DDL herdadas de um step de migração deletou 3 tabelas de produção e causou 4 horas de outage. O problema não era o agent ter poder demais — era não ter feedback loop quando uma ação era negada ou permitida incorretamente. O PermissionDenied hook é exatamente esse feedback loop para o Claude Code.

O Stack Completo: PermissionDenied + PreToolUse + Auto Mode

O PermissionDenied hook não opera sozinho. Ele faz parte de um ecossistema de hooks que, combinados, dão governança completa:

Hook Quando dispara O que pode fazer
PreToolUse Antes da execução Bloquear, permitir, pedir confirmação, defer
PermissionRequest Quando diálogo de permissão apareceria Aprovar ou negar automaticamente
PermissionDenied Após classifier negar (auto mode only) Permitir retry, logar auditoria
PostToolUse Após execução bem-sucedida Logar, notificar, validar resultado

O padrão robusto para CI/CD combina três:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [{
          "type": "command",
          "if": "Bash(rm -rf *)",
          "command": "echo 'Blocked: destructive rm -rf' >&2 && exit 2"
        }]
      }
    ],
    "PermissionDenied": [
      {
        "matcher": "",
        "hooks": [{
          "type": "command",
          "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/retry-safe-commands.sh"
        }]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [{
          "type": "command",
          "command": "jq -r '.tool_input.command' >> ~/.claude/command-audit.log"
        }]
      }
    ]
  }
}

PreToolUse bloqueia o que é perigoso ANTES de executar. PermissionDenied recupera o que foi negado indevidamente. PostToolUse loga tudo que executou. Defesa em profundidade, não permissividade.

Para distribuir essa configuração em 20 máquinas da sua SH, use managed-settings.d (settings gerenciadas via servidor):

// managed-settings.d/hooks.json (distribuído via endpoint)
{
  "hooks": {
    "PermissionDenied": [
      {
        "matcher": "",
        "hooks": [{
          "type": "http",
          "url": "https://sua-sh.com/api/claude-hooks/permission-denied",
          "headers": { "Authorization": "Bearer $CLAUDE_HOOK_TOKEN" },
          "allowedEnvVars": ["CLAUDE_HOOK_TOKEN"]
        }]
      }
    ]
  }
}

Hook HTTP permite centralizar a lógica de retry em um servidor — uma mudança no endpoint atualiza todos os devs sem tocar nas máquinas. Combine com forceRemoteSettingsRefresh para garantir que o hook está ativo antes de qualquer sessão começar.

O Que Eu Penso

Na minha experiência com 300+ software houses, o padrão que mais vejo é binário: ou o time usa --dangerously-skip-permissions e reza, ou deixa o auto mode puro e aceita as interrupções. Não existe meio-termo na cabeça da maioria.

O PermissionDenied hook é o meio-termo que faltava. É a diferença entre “meu pipeline é frágil” e “meu pipeline se cura sozinho para cenários conhecidos e para quando não conhece.”

Mas o que mais me preocupa é o dado da Gravitee: 88% das organizações já tiveram incidentes de segurança com AI agents. E a maioria — 50%+ — opera sem logging. Sem o hook de auditoria, você não sabe quantas vezes por dia o classifier está negando comandos dos seus devs, nem quais comandos, nem por quê.

O PermissionDenied hook não é só sobre retry. É sobre visibilidade. É sobre saber o que está acontecendo na sua frota de Claude Code antes que vire um incidente.

Conclusão

Se você tem Claude Code em CI/CD e ainda não configurou o PermissionDenied hook, você está aceitando que:

  • 8.5% dos comandos seguros serão negados no Stage 1
  • Cada falso positivo em headless mata o pipeline inteiro
  • Nenhuma negação é logada para auditoria ou análise
  • Nenhum retry automático acontece para comandos que você autorizaria sem pensar

Três coisas pra fazer hoje:

  1. Crie o script retry-safe-commands.sh com os comandos que seu pipeline realmente usa
  2. Adicione o hook no settings.json (projeto ou managed-settings)
  3. Monitore o log de negações por uma semana — os padrões vão te surpreender

Se você quer implementar esse nível de governança de IA na sua software house, começar pelo pipeline é o primeiro passo.

Sou Thulio, mentoro 300+ SHs desde 2016.

Marcado:

Deixe um Comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *