La fase de construcción usó hooks como barreras de seguridad, permitiendo o bloqueando acciones sin que interviniera ningún humano (Etapa 3: Construir). Un hook también puede preguntar, pausando la acción hasta que la persona que ejecuta la sesión la confirme. Cuando el aprobador es otra persona, como un gestor de lanzamientos, el hook busca su aprobación registrada y bloquea hasta que esta exista.
La jugada se ubica en la Etapa 5: Desplegar porque la puerta de lanzamiento es el caso más claro, pero los hooks no son específicos del despliegue: se ejecutan en cualquier lugar donde Claude actúe. Por ejemplo, los hooks pueden bloquear ediciones a migraciones e infraestructura sin un ticket de cambio durante la Etapa 3: Construir, y detener al agente de editar archivos de prueba durante una tarea de corrección en la Etapa 4: Probar.
Primeros pasos
- Requisitos previos: Ninguno.
- Infraestructura: Una lista escrita de las aprobaciones que requiere el proceso de cambio.
Cómo ejecutarlo
- El liderazgo de ingeniería, junto con gestión de cambios y cumplimiento, enumera las puertas de aprobación humana que deben mantenerse, como la aprobación de gestión de cambios, la autorización de lanzamiento y las ediciones a rutas protegidas.
- El ingeniero de plataforma expresa cada puerta como un hook, un script que se ejecuta antes de que Claude actúe y que puede permitir, preguntar o bloquear.
- Los hooks de equipo van en
.claude/settings.jsonen Git, y los hooks no negociables van en la configuración administrada propiedad de la plataforma o del administrador de TI, donde los ingenieros individuales no pueden desactivarlos. - Un bloqueo debe explicarse por sí mismo, así que cuando un hook detiene una acción, la razón y la ruta hacia la aprobación aparecen en la salida de Claude.
Cómo se ve
Este ejemplo es el portal de clientes de una aseguradora, desplegado en dev, staging y producción. El despliegue se ejecuta a través de herramientas MCP, un servidor por entorno, y cada servidor tiene una herramienta release.
La entrada de abajo es un ejemplo independiente en el .claude/settings.json del proyecto. Su matcher captura release en cada servidor. La lista vacía args hace que Claude Code ejecute el script directamente en lugar de a través de un shell:
{
"hooks": {
"PreToolUse": [
{
"matcher": "mcp__deploy-.*__release",
"hooks": [
{ "type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/release-gate.sh",
"args": [] }
]
}
]
}
}La puerta en sí, .claude/hooks/release-gate.sh:
#!/bin/bash
# Puerta de lanzamiento. dev: permitir. staging: preguntar. producción: un ticket de cambio aprobado para esta versión, o bloquear.
input=$(cat)
tool=$(jq -r '.tool_name' <<<"$input")
version=$(jq -r '.tool_input.version' <<<"$input")
ticket=$(jq -r '.tool_input.ticket // empty' <<<"$input")
decide() { # decide <allow|ask|deny> <reason>
jq -n --arg decision "$1" --arg reason "$2" '{hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: $decision,
permissionDecisionReason: $reason}}'
exit 0
}
case "$tool" in
mcp__deploy-dev__release)
decide allow "dev needs no approval" ;;
mcp__deploy-staging__release)
[ "$GITHUB_REF" = "refs/heads/main" ] && decide allow "pipeline release from main"
decide ask "This ships $version to staging. Approve only if you own this release." ;;
mcp__deploy-prod__release)
route="Open a change ticket at https://change.example.com/new for version $version, \
wait for the release manager to approve it, then call release again with that ticket number."
[[ "$ticket" =~ ^CHG-[0-9]+$ && "$version" =~ ^[0-9a-f]{7,40}$ ]] ||
decide deny "Production needs an approved change ticket. $route"
if approver=$(/usr/local/bin/change-ticket show --ticket "$ticket" --format json |
jq -er --arg v "$version" 'select(.state == "approved" and .version == $v) | .approver' 2>/dev/null); then
decide allow "$ticket approved by $approver"
fi
decide deny "$ticket is not approved for version $version. $route" ;;
*)
decide deny "No release rule for $tool. Add one to release-gate.sh first." ;;
esacEn el script, version y ticket son las entradas que Claude pasa a la herramienta release. change-ticket representa la propia herramienta de línea de comandos de tu sistema de cambios. La puerta depende de que el agente no tenga forma de escribir en los registros que lee.
En una ejecución no interactiva nadie puede responder a un ask, así que Claude Code niega la llamada. Por eso la puerta permite staging desde el pipeline en main, donde la fusión aprobada es la aprobación.
La puerta tiene un límite. Un ingeniero podría establecer GITHUB_REF manualmente en su propia sesión, aunque hacer eso solo omite la indicación que ellos mismos habrían respondido.
La razón en un ask se muestra a la persona. La razón en un deny se muestra a Claude, así que el mensaje de bloqueo nombra la ruta hacia la aprobación para que Claude la transmita. El resultado de staging aparece primero, luego producción sin ticket:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "ask",
"permissionDecisionReason": "This ships 4f2c9e1 to staging. Approve only if you own this release."
}
}{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Production needs an approved change ticket. Open a change ticket at https://change.example.com/new for version 4f2c9e1, wait for the release manager to approve it, then call release again with that ticket number."
}
}Consideraciones de gobernanza
Los hooks son las puertas de aprobación. La condición de la puerta se aplica cada vez, para todos. Las decisiones de permitir y bloquear se registran con una marca de tiempo. La puerta también define qué cuenta como aprobación, ya sea un ticket de cambio aprobado o la firma del gestor de lanzamientos.
Configuración administrada para una empresa regulada
Configuración administrada para una empresa regulada, desplegada por el equipo de plataforma mediante gestión de dispositivos móviles (MDM) o la consola de administración. Los ingenieros no pueden editar ni anular ninguna de las configuraciones allí contenidas. Ver a continuación:
{
"permissions": {
"deny": [
"Read(.env*)", "Read(./secrets/**)",
"WebFetch", "Bash(curl *)", "Bash(wget *)"
],
"allow": [
"Bash(git *)", "Bash(make build)",
"Bash(make test)", "Bash(make lint)"
],
"disableBypassPermissionsMode": "disable"
},
"allowManagedPermissionRulesOnly": true,
"sandbox": {
"enabled": true,
"failIfUnavailable": true,
"allowUnsandboxedCommands": false,
"network": { "allowedDomains": ["git.internal.example.com", "registry.npmjs.org"] },
"credentials": {
"files": [
{ "path": "~/.ssh", "mode": "deny" },
{ "path": "~/.aws/credentials", "mode": "deny" }
],
"envVars": [ { "name": "GITHUB_TOKEN", "mode": "deny" } ]
}
},
"allowManagedHooksOnly": true,
"disableSideloadFlags": true,
"allowManagedMcpServersOnly": true,
"strictKnownMarketplaces": [
{ "source": "github", "repo": "example-corp/approved-plugins" }
],
"requiredMinimumVersion": "2.1.193"
}Lo que hacen las configuraciones, en términos de control:
permissions.denymantiene los secretos fuera del contexto del agente y bloquea la salida de red arbitraria a través de herramientas.permissions.allowpreaprueba el bucle interno seguro para que la lista de denegación no se convierta en fatiga de indicaciones.disableBypassPermissionsModejunto conallowManagedPermissionRulesOnlysignifica que ningún ingeniero, archivo de proyecto o indicador de línea de comandos puede ampliar las reglas.sandboxcubre lo que los permisos no pueden. Una denegación a nivel de herramienta en WebFetch no impide que un comando de shell alcance la red, mientras que la lista de permitidos de dominio a nivel de sistema operativo bloquea la salida por completo, así que ambos aplican un mismo objetivo en capas diferentes.failIfUnavailableyallowUnsandboxedCommandsconvierten el sandbox en una precondición, lo que significa que Claude Code se niega a iniciar cuando el sandbox no puede inicializarse y un comando que falla dentro del sandbox no puede reintentarse fuera de él.credentialsmaneja un caso que las reglas de denegación no cubren.permissions.denygobierna las herramientas de archivos de Claude, pero un comando de shell en sandbox aún podría leer~/.ssho~/.aws/credentialspor defecto. Este bloque deniega esas lecturas y elimina los secretos listados del entorno de los comandos en sandbox.allowManagedHooksOnlysignifica que solo se ejecutan los hooks definidos en la configuración administrada; los hooks en la configuración de usuario, proyecto y local están bloqueados, incluyendo el ejemplo independiente de.claude/settings.jsonanterior. Para mantener aplicada la puerta de aprobación de esta jugada, defínela en el propio bloquehooksdel archivo administrado.disableSideloadFlagsystrictKnownMarketplacessignifican que cualquier skill, agente, hook o servidor MCP en la máquina de un ingeniero proviene del marketplace de plugins aprobado por la organización y no de un directorio personal. La lista de permitidos del marketplace controla qué se puede instalar, y los indicadores que cargarían lateralmente un plugin, agente o configuración MCP para una sola ejecución se rechazan al inicio.allowManagedMcpServersOnlyhace que la superficie de herramientas del agente sea una lista de permitidos propiedad del equipo de plataforma.requiredMinimumVersionse niega a iniciar en una versión por debajo del mínimo aprobado, de modo que los controles se aplican mediante una compilación que la organización realmente ha evaluado.
Trata el ejemplo como un punto de partida para personalizar según tu propio entorno. Cada regla de denegación elimina alguna capacidad, y el equilibrio correcto depende de la clasificación de datos del repositorio. La referencia de configuración(se abre en una pestaña nueva) documenta todas las claves, incluidas las exclusivas de administración.
Cómo medirlo
Para los hooks en sí:
- Indicador adelantado: Tiempo dedicado a esperar en cada puerta de aprobación. Cada decisión de hook se escribe en la exportación de OpenTelemetry con una marca de tiempo y un veredicto de permitir o bloquear, de modo que la espera es visible por puerta.
- Indicador rezagado: Violaciones de puerta que llegan a producción antes y después de los hooks, según el rastreador de incidentes.