Home / Claude Code / Conditional Hooks: Seus Hooks Estão Disparando à Toa (E Custando Performance)

Conditional Hooks: Seus Hooks Estão Disparando à Toa (E Custando Performance)

# Step 03 — Article Generator Output (v18)

“`json
{
“article”: {
“title”: “Conditional Hooks: Seus Hooks Estão Disparando à Toa (E Custando Performance)”,
“slug”: “claude-code-conditional-hooks-if-performance-software-house”,
“meta_description”: “O campo if dos hooks do Claude Code filtra exatamente quando cada hook executa. Menos overhead, mais governança. Essencial para software houses com automação IA.”,
“category”: “Claude Code”,
“tags”: [“claude-code”, “ia”, “software-house”, “hooks”, “automacao”, “devops”, “performance”, “ci-cd”, “governanca”, “conditional-hooks”],
“feature_version”: “v2.1.85”,
“feature_name”: “Conditional Hooks if field”,
“cover_image_prompt”: “Editorial photo of a precision Swiss watchmaker’s workshop with magnifying loupe over intricate gears, one gear highlighted in electric blue glow representing the exact mechanism being triggered, dark moody lighting with selective focus, tech-precision aesthetic, 16:9 aspect ratio, premium quality”,
“sources”: [
“https://code.claude.com/docs/en/hooks-guide”,
“https://code.claude.com/docs/en/hooks”,
“https://code.claude.com/docs/en/changelog”,
“https://x.com/lydiahallie/status/2037573738670297583”,
“https://www.pixelmojo.io/blogs/claude-code-hooks-production-quality-ci-cd-patterns”,
“https://serenitiesai.com/articles/claude-code-hooks-guide-2026”
],
“body_markdown”: “BODY_BELOW”
}
}
“`

Você configurou hooks no Claude Code. Ótimo. Seu hook de segurança que bloqueia `rm -rf` está disparando toda vez que o Claude roda um `ls`. Seu hook de auditoria de git está spawnando um processo novo quando o Claude faz `npm test`. Seu hook de lint está executando quando o Claude lê um arquivo JSON.

Cada um desses processos desnecessários custa milissegundos. Multiplica por 10 hooks, multiplica por dezenas de tool calls por sessão, e você tem segundos de overhead puro — seu Claude Code fica lento sem motivo nenhum.

Na minha experiência com 300+ software houses, hooks são a feature mais subutilizada do Claude Code. Todo mundo configura o matcher genérico e acha que tá resolvido. Não tá. E desde a versão 2.1.85, existe uma solução elegante que quase ninguém conhece: o campo `if`.

## O que é o campo `if` nos hooks

Antes do `if`, hooks tinham apenas uma camada de filtro: o **matcher**. O matcher filtra por nome da ferramenta — “rode esse hook quando for Bash”, “rode quando for Edit ou Write”. Funciona, mas é grosseiro demais.

O `if` adiciona uma segunda camada de filtro que olha não só QUAL ferramenta, mas COM QUAIS ARGUMENTOS ela está sendo chamada. Usa a mesma [permission rule syntax](https://code.claude.com/docs/en/permissions) das permissões do Claude Code.

“`json
{
“hooks”: {
“PreToolUse”: [
{
“matcher”: “Bash”,
“hooks”: [
{
“type”: “command”,
“if”: “Bash(git *)”,
“command”: “.claude/hooks/check-git-policy.sh”
}
]
}
]
}
}
“`

Nesse exemplo, o hook de política de git **só spawna** quando o Claude executa um comando Bash que começa com `git`. Se o Claude rodar `npm test`, `ls -la`, `cat arquivo.txt` — nada acontece. Zero overhead.

A [Lydia Hallie destacou no X](https://x.com/lydiahallie/status/2037573738670297583): *”Claude Code now supports an `if` field in hooks. It uses permission rule syntax to filter when a hook runs, which is useful when you want a hook on some bash commands but not every single one!”*

Exatamente. Não é sobre ter hooks. É sobre ter hooks que sabem quando ficar quietos.

## Como funciona na prática: a filtragem em duas camadas

Pensa assim:

“`
Evento dispara (ex: PreToolUse)

├─ Camada 1: MATCHER
│ Pergunta: “A ferramenta é Bash?”
│ Se sim → continua
│ Se não → descarta (sem overhead)

└─ Camada 2: IF (novo!)
Pergunta: “O comando Bash começa com ‘rm’?”
Se sim → spawna o hook
Se não → descarta (sem overhead!)
“`

A mágica é que a condição `if` é avaliada **antes** de spawnar o processo. Não é o hook que decide se deve rodar — o Claude Code já sabe antes mesmo de criar o processo filho. Isso elimina o overhead na raiz.

### Patterns que você pode usar

A syntax é `NomeDaFerramenta(padrão)`:

“`
“Bash(git *)” → Comandos Bash que começam com “git”
“Bash(rm *)” → Comandos que começam com “rm”
“Bash(sudo *)” → Comandos com sudo
“Edit(*.ts)” → Edit em arquivos TypeScript
“Edit(*.tsx)” → Edit em arquivos TSX
“Bash(npm test)” → Exatamente “npm test”
“Bash(*docker*)” → Qualquer comando com “docker” no meio
“`

O `*` funciona como wildcard — qualquer sequência de caracteres.

## 4 casos de uso que toda software house deveria implementar

### 1. Bloquear comandos destrutivos (sem atrasar o resto)

Sem `if`:
“`json
{
“matcher”: “Bash”,
“hooks”: [{
“type”: “command”,
“command”: “.claude/hooks/block-destructive.sh”
}]
}
“`
Problema: `block-destructive.sh` spawna pra CADA comando Bash. Se o Claude roda 50 comandos numa sessão, são 50 processos — 49 desnecessários.

Com `if`:
“`json
{
“matcher”: “Bash”,
“hooks”: [
{
“type”: “command”,
“if”: “Bash(rm -rf *)”,
“command”: “.claude/hooks/block-rf.sh”
},
{
“type”: “command”,
“if”: “Bash(sudo *)”,
“command”: “.claude/hooks/block-sudo.sh”
}
]
}
“`
Agora: `block-rf.sh` só spawna quando alguém tenta `rm -rf`. `block-sudo.sh` só quando tem `sudo`. Os outros 48 comandos? Zero overhead.

### 2. Lint apenas em arquivos que importam

“`json
{
“hooks”: {
“PostToolUse”: [
{
“matcher”: “Edit|Write”,
“hooks”: [
{
“type”: “command”,
“if”: “Edit(*.ts)|Write(*.ts)”,
“command”: “jq -r ‘.tool_input.file_path’ | xargs npx eslint –fix”
}
]
}
]
}
}
“`

O linter só roda quando TypeScript é editado. Editou um `.json`? Criou um `.md`? Sem lint desnecessário.

### 3. Auditoria cirúrgica de operações git

“`json
{
“hooks”: {
“PostToolUse”: [
{
“matcher”: “Bash”,
“hooks”: [
{
“type”: “command”,
“if”: “Bash(git push *)”,
“command”: “.claude/hooks/audit-push.sh”
},
{
“type”: “command”,
“if”: “Bash(git commit *)”,
“command”: “.claude/hooks/audit-commit.sh”
}
]
}
]
}
}
“`

Você audita exatamente `push` e `commit` — as operações que mudam estado. `git status`, `git diff`, `git log`? Passam direto sem spawn.

### 4. Gate de permissão para operações de banco

“`json
{
“hooks”: {
“PreToolUse”: [
{
“matcher”: “Bash”,
“hooks”: [
{
“type”: “command”,
“if”: “Bash(*DROP *)”,
“command”: “.claude/hooks/block-drop.sh”
},
{
“type”: “command”,
“if”: “Bash(*DELETE FROM *)”,
“command”: “.claude/hooks/confirm-delete.sh”
}
]
}
]
}
}
“`

## A conta que ninguém faz (mas deveria)

Vamos fazer uma conta rápida. Digamos que sua SH tem 5 hooks configurados em `PreToolUse`, todos sem `if`. Cada hook leva 200ms para spawnar e executar (conservador — hooks com HTTP ou jq levam mais).

Numa sessão típica, o Claude faz ~80 tool calls. Cada tool call dispara todos os 5 hooks:

– **Sem `if`:** 80 × 5 × 200ms = **80 segundos** de overhead puro por sessão
– **Com `if`:** Digamos que só 10% dos tool calls são relevantes: 8 × 5 × 200ms = **8 segundos**

São **72 segundos** economizados por sessão. Se seu time de 10 devs roda 5 sessões por dia cada, são **60 minutos de overhead eliminados por dia**. Por mês? **~20 horas**.

E isso é só performance. O ganho de **clareza em logs** é igualmente valioso — quando um hook dispara, você sabe que era relevante, não ruído.

## O bug fix que confirma a adoção

Na versão 2.1.88 (um release depois), a Anthropic corrigiu um bug: *”Fixed hooks `if` condition filtering not matching compound commands (`ls && git push`) or commands with env-var prefixes (`FOO=bar git push`)”*.

Isso é significativo por dois motivos:

1. **A comunidade adotou rápido** — encontraram edge cases em uso real
2. **Compound commands são comuns** — `cd /app && git pull && npm install` é o tipo de pipeline que todo dev roda

Depois do fix, `if: “Bash(git *)”` também casa com `FOO=bar git push` e `ls && git push`. O matcher ficou robusto para uso em produção.

## Limitação importante: só funciona em tool events

O `if` só funciona em 4 tipos de evento:

– **PreToolUse** — antes da ferramenta executar
– **PostToolUse** — depois da ferramenta executar com sucesso
– **PostToolUseFailure** — depois da ferramenta falhar
– **PermissionRequest** — quando aparece diálogo de permissão

Se você colocar `if` em eventos como `SessionStart`, `Stop`, `Notification`, `CwdChanged` — **o hook nunca executa**. Faz sentido: esses eventos não têm “ferramenta” nem “argumentos” para filtrar.

## O ecossistema de hooks como camada de governança

Se você acompanha essa série, já falamos sobre [HTTP Hooks para controle centralizado](/blog/claude-code-http-hooks-controle-centralizado-software-house) e [StopFailure Hook como alarme de automação](/blog/claude-code-stopfailure-hook-alarme-automacao-ia). O `if` é a peça que faltava para tornar hooks viáveis em **escala**.

Sem filtro fino, uma SH com 15-20 hooks criava um gargalo de performance absurdo. Com `if`, você pode ter 50 hooks hiperespecíficos, cada um disparando cirurgicamente, com overhead total menor do que 5 hooks genéricos.

Isso muda a mentalidade: hooks deixam de ser “automação que atrasa” e viram “governança que acelera”. Você pode ter hooks para:
– Bloquear padrões perigosos em SQL
– Auditar pushes para branches protegidas
– Lint apenas no tipo de arquivo correto
– Validar formatação apenas em PRs para main
– Notificar apenas quando operações críticas acontecem

Tudo isso rodando **zero overhead** quando o Claude está fazendo tarefas cotidianas como ler arquivos ou rodar testes.

## O que eu penso

Hooks sempre foram a feature mais “enterprise” do Claude Code. Mas até a versão 2.1.85, implementar hooks seriamente era uma troca: mais controle em troca de mais lentidão. O `if` quebrou essa troca.

Na minha experiência mentorando software houses, os times que mais se beneficiam de IA são os que tratam IA como infraestrutura, não como brinquedo. E infraestrutura precisa ser rápida, precisa e observável. O `if` é o que transforma hooks de “scripts que rodam” em “políticas que governam”.

Se sua SH ainda usa hooks sem `if`, revise hoje. O ganho é imediato e o risco é zero — `if` é retrocompatível, hooks sem `if` continuam funcionando exatamente como antes.

## Como implementar agora

1. Abra seus settings: `.claude/settings.json` ou `~/.claude/settings.json`
2. Identifique hooks em `PreToolUse` e `PostToolUse` que hoje rodam pra tudo
3. Adicione `”if”: “NomeFerramenta(padrão)”` em cada handler
4. Teste com `/hooks` no Claude Code para verificar
5. Monitore com `Ctrl+O` (verbose mode) para ver quais hooks estão disparando

“`json
// Antes (spawna pra TODO comando Bash)
{ “type”: “command”, “command”: “meu-hook.sh” }

// Depois (spawna SÓ pra git)
{ “type”: “command”, “if”: “Bash(git *)”, “command”: “meu-hook.sh” }
“`

Uma linha de configuração. Impacto imediato na performance.

## Conclusão

O Claude Code não é uma ferramenta pronta — é uma plataforma que você configura. E hooks com `if` são a diferença entre configurar com martelo e configurar com bisturi.

Se você quer implementar esse nível de governança e performance na IA da sua software house, começa por aí. É uma linha de JSON que muda a equação de overhead de toda sua automação.

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 *