Uma equipe de plataforma habilita Claude Code em toda a sua organização de engenharia. Para aplicar políticas sobre chamadas de ferramentas, eles conectam um servidor de hook — um processo local que recebe eventos de hook na entrada padrão (stdin) e retorna uma decisão em JSON na saída padrão (stdout). A primeira semana parece bem. PreToolUse é disparado antes de cada chamada de ferramenta, o hook retorna deny para qualquer coisa que toque produção, e a equipe acredita que a aplicação da política está funcionando.
Então, Claude Code lança uma nova versão. PermissionRequest começa a disparar junto com PreToolUse. O servidor de hook retorna o mesmo JSON permissionDecision que já estava retornando. Claude Code aceita a resposta, faz o parsing, não encontra nenhum campo que reconheça para aquele evento, e prossegue como se nenhuma decisão tivesse sido dada. A negação é silenciosamente ignorada. O ponto de aplicação da política da equipe agora é um muro com gap nele, e nada nos logs indica isso.
Este é o problema que um PEP governado precisa resolver: o ciclo de vida do hook do Claude Code não é um único evento com um formato de fio único. São aproximadamente 30 eventos, cada um com um esquema de saída diferente que o runtime reconhece, e um erro no esquema é indistinguível do silêncio.
O mecanismo de fio é o contrato de segurança
A razão pela qual um único campo permissionDecision não funciona em todos os lugares é que os eventos de hook do Claude Code evoluíram de forma independente. A forma de saída que controla uma chamada de ferramenta não é a forma que controla o envio de um prompt, que não é a forma que interrompe uma tarefa.
Existem seis mecanismos de fio distintos:
| Mecanismo | Forma de saída | Eventos |
|---|---|---|
permissionDecision | hookSpecificOutput.permissionDecision (permitir /deny/ask/defer) | UsoAvantFerramenta |
permissionBehavior | hookSpecificOutput.decision.behavior (permitir /deny) | SolicitaçãoDePermissão |
topLevelDecision | Nível superior decision: "block" + reason | EnvioDePromptDoUsuário, ExpansãoDePromptDoUsuário, PreCompactação, MudançaDeConfiguração, PósLoteDeFerramenta |
continueFalse | continue: false + stopReason | TarefaCriada, TarefaConcluída, ColegaDeEquipeOcioso |
postToolUse | decision: "block" (bloqueia processamento adicional; ferramenta já executada) | PósUsoDeFerramenta |
neutral | Nenhum bloqueio aplicável | Todos os eventos context/observe, além de eventos invertidos como Stop |
Se um PEP retornar permissionDecision: "deny" em um evento PermissionRequest, Claude Code o ignora — esse evento espera decision.behavior, não permissionDecision. O JSON é válido, a resposta HTTP é 200, e a negação não existe. Este não é um caso limite teórico; é o contrato de comunicação documentado, verificado contra code.claude.com/docs/en/hooks.
Classificação em três vias: controle, contexto, observação
Nem todo evento de hook pode controlar uma ação. Tentar negar um SessionEnd ou um Notification é sem sentido — esses eventos são informativos e não carregam controle de decisão. Tentar negar um evento Stop é pior que sem sentido: decision: "block" em Stop mantém o agente funcionando, o que é o oposto de uma parada de segurança.
O PEP classifica cada evento de hook reconhecido em uma das três categorias:
Eventos de Gating carregam uma decisão que pode permitir, negar ou bloquear uma ação. Eles são a superfície de aplicação. Mas nem todos os eventos de gating são igualmente aplicáveis: Stop e SubagentStop são classificados como gating na própria taxonomia de Claude Code, mas sua semântica de bloqueio é invertida (bloquear = continuar executando), então o PEP os trata como neutros — ele nunca emite um bloqueio que manteria um agente vivo contra a intenção do operador. De forma semelhante, Elicitation e ElicitationResult têm uma classificação de gating, mas não possuem um mecanismo de ação conectado na versão atual, então o PEP os define como neutros em vez de fingir que a aplicação existe onde ela não existe.
Contexto eventos permitem que o PEP injete additionalContext ou reescreva a saída, mas não podem realmente bloquear a ação. PostToolUse é a exceção partial — ela pode bloquear o processamento futuro de uma saída sinalizada, mas a ferramenta já foi executada. O resto (PermissionDenied, MessageDisplay, SessionStart, Setup, SubagentStart, PostCompact, InstructionsLoaded, PostToolUseFailure) são de observar-com-contexto: úteis para enriquecer a visão do modelo, não para pará-lo.
Observar eventos (Notification, SessionEnd, StopFailure, CwdChanged, FileChanged, WorktreeCreate, WorktreeRemove) não têm controle de decisão algum. O PEP os registra para o caminho de telemetria e o inventário SIEM, responde de forma neutra e segue em frente.
Esta classificação não é consultiva. Ela determina se o decidor da raiz de composição aplica uma regra de política (gating + aplicável), injeta contexto (contexto) ou apenas observa (observar). Errar significa ou aplicação falsa (retornando um negar que é ignorado) ou aplicação perdida (tratando um evento de gating como observar).
O mapa hookSpecs: fonte única da verdade
A classificação, o mecanismo de conexão e a flag de aplicabilidade vivem em um mapa. Este é o código real que o conector usa — tanto o renderizador de resposta HTTP quanto o decidor da raiz de composição leem a partir dele:
type hookSpec struct {
class string // "gating" | "context" | "observe"
mech hookMech
enforceable bool
}
var hookSpecs = map[string]hookSpec{
// GATING — o retorno de um hook pode aplicar allow/deny/block à ação.
"PreToolUse": {"gating", mechPermissionDecision, true},
"PermissionRequest": {"gating", mechPermissionBehavior, true},
"UserPromptSubmit": {"gating", mechTopLevelDecision, true},
"UserPromptExpansion": {"gating", mechTopLevelDecision, true},
"PreCompact": {"gating", mechTopLevelDecision, true},
"ConfigChange": {"gating", mechTopLevelDecision, true},
"PostToolBatch": {"gating", mechTopLevelDecision, true},
"TaskCreated": {"gating", mechContinueFalse, true},
"TaskCompleted": {"gating", mechContinueFalse, true},
"TeammateIdle": {"gating", mechContinueFalse, true},
"Stop": {"gating", mechNeutral, false}, // invertido
"SubagentStop": {"gating", mechNeutral, false}, // invertido
"Elicitation": {"gating", mechNeutral, false}, // não ligado na v1
"ElicitationResult": {"gating", mechNeutral, false},
// CONTEXT — additionalContext / reescrita da saída, sem bloqueio real.
"PostToolUse": {"context", mechPostToolUse, true},
"PostToolUseFailure": {"context", mechNeutral, false},
"PermissionDenied": {"context", mechNeutral, false},
"MessageDisplay": {"context", mechNeutral, false},
"SessionStart": {"context", mechNeutral, false},
"Setup": {"context", mechNeutral, false},
"SubagentStart": {"context", mechNeutral, false},
"PostCompact": {"context", mechNeutral, false},
"InstructionsLoaded": {"context", mechNeutral, false},
// OBSERVE — sem controlo de decisão.
"Notification": {"observe", mechNeutral, false},
"SessionEnd": {"observe", mechNeutral, false},
"StopFailure": {"observe", mechNeutral, false},
"CwdChanged": {"observe", mechNeutral, false},
"FileChanged": {"observe", mechNeutral, false},
"WorktreeCreate": {"observe", mechNeutral, false},
"WorktreeRemove": {"observe", mechNeutral, false},
}
Trinta eventos, cada um com exatamente uma classificação, um mecanismo de fio e um veredito de aplicabilidade. O renderizador consulta hookMechFor(event) para decidir qual formato JSON emitir. O decisor consulta HookEnforcementFor(event) para decidir se deve aplicar uma regra de política, injetar contexto ou observar. Ambos leem do mesmo mapa, então não podem discordar.
O padrão de negação
A função mais importante no arquivo tem quatro linhas de comprimento:
func hookSpecFor(event string) hookSpec {
if s, ok := hookSpecs[event]; ok {
return s
}
return hookSpec{class: "unknown", mech: mechPermissionDecision, enforceable: true}
}
Um evento que não está no mapa — porque Claude Code enviou um novo evento de gancho que o conector ainda não classificou — é tratado como unknown, atribuído ao mecanismo de fio mechPermissionDecision e marcado como aplicável. Este é o padrão de negação-fechada: um evento não reconhecido é um portão de permissão, não uma passagem silenciosa. Se nenhuma regra de política corresponder, aplica-se a postura padrão configurada pelo operador, que em uma implantação governada é negar.
A alternativa — definir como neutro ou observar — significaria que cada novo evento de gancho que Claude Code introduz ficaria sem controle até que alguém o notasse e adicionasse ao mapa. Em um modelo de negação-fechada, o novo evento é governado desde o momento em que ocorre, mesmo que a classificação seja conservadora. Uma negação falsa em um novo evento é visível e corrigível; uma permissão silenciosa é invisível e pode persistir por meses.
A struct HookEnforcement exporta esta classificação para que o decisor possa distinguir três níveis de eventos de gate:
- Gate clássico (
PreToolUse,PermissionRequest,PostToolUsee qualquer evento desconhecido): quando nenhuma regra governada é correspondida, o padrão da política do operador se aplica (negar-fechado). - Gate de ciclo de vida (outros eventos de gate aplicáveis, como
UserPromptSubmit,TaskCreated): o decisor aplica um padrão seguro por evento — neutro para eventos UX/lifecycle, negar para eventos de mutação de estado. - Gate não aplicável (
Stop,SubagentStop,Elicitation): o decisor retorna neutro independentemente. Emitir um negar que Claude Code interpreta como “continuar executando” seria o oposto de seguro.
Distribuição: da classificação para a frota
Classificar eventos corretamente é uma metade. Colocar o PEP em cada instância Claude Code é a outra metade. O conector de configurações gerenciadas transforma a configuração do hook na forma que Claude Code espera e a distribui como um arquivo de configurações gerenciadas pelo servidor — uma configuração não substituível que o plano de controle envia para cada host gerenciado.
O hook PEP é distribuído com um matcher vazio (corresponde a todas as ferramentas — nenhuma ferramenta escapa do ponto de aplicação) e o associa com allowManagedHooksOnly para evitar que hooks locais de um desenvolvedor neutralizem o PEP gerenciado. Sem esse flag, um hook de nível de usuário poderia sobrepor o gerenciado, e a aplicação pareceria presente enquanto poderia ser contornada. A validação no momento da autoria detecta isso: se uma política envia um hook PEP PreToolUse sem allowManagedHooksOnly, o console emite um aviso de anti-manipulação.
O caminho de telemetria é separado e não bloqueado por plano: a exportação OpenTelemetry de Claude Code é habilitada por meio de variáveis de ambiente gerenciadas (CLAUDE_CODE_ENABLE_TELEMETRY, as chaves de exportador OTEL_*) para que o plano de controle possa observar o uso da assinatura sem fazer proxy de inferência ou acessar a credencial da assinatura. A captura de conteúdo (OTEL_LOG_USER_PROMPTS, OTEL_LOG_TOOL_CONTENT) é desativada por padrão; ativá-la é uma escolha deliberada e sinalizada porque envia o conteúdo de prompt e ferramenta para fora da máquina do desenvolvedor, criando uma obrigação de residência e redação que o plano de controle deve assumir.
O que isso significa para uma implantação regulamentada
Uma equipe executando Claude Code com um PEP regulamentado obtém algumas propriedades que importam:
- Sem permissão silenciosa. Todo evento de hook é classificado, e qualquer evento não reconhecido tem como padrão a negação. Um novo lançamento Claude Code não pode introduzir um evento de ciclo de vida sem governança.
- Formato de wire correto por evento. O PEP não retorna um objeto JSON genérico esperando que o Claude Code o aceite. Ele retorna o esquema de saída exato que o evento específico espera, porque uma negação no esquema errado não é uma negação.
- Não aplicação honesta. Eventos que não podem ser aplicados (eventos de observação, eventos de bloqueio invertido) não são aplicados de forma fictícia. O PEP os registra, retorna neutro e não dá ao operador uma falsa sensação de controle.
- Anti-fraude na distribuição. A camada de configurações gerenciadas garante que o hook do PEP não possa ser sobrescrito e sinaliza configurações onde ele poderia ser comprometido.
O PEP é a metade de aplicação de um modelo operacional governado maior. O mapa de acesso, o registro de auditoria e a camada de política-como-código ao redor dele são abordados na visão geral do produto e na documentação de hooks. Se você quiser ver como o caminho de telemetria e o portão de permissões se compõem, a visão geral da arquitetura percorre ambos.