Disparar un Comando de Shell en PostToolUse con Hooks
Un hook PostToolUse es un comando de shell que se ejecuta automáticamente justo después de que una llamada a herramienta coincidente finaliza.
El uso más común es el autoformateo: en el momento en que Claude edita un archivo, un formateador se ejecuta sobre él sin que nadie lo pida.
Resumen
Los hooks se configuran en settings.json, bajo un nombre de evento como PostToolUse.
Cada entrada de hook tiene un matcher, que lo limita a herramientas específicas como Edit o Write, y uno o más comandos hooks para ejecutar cuando ese matcher se dispara.
A diferencia de un comando de barra (/), nada sobre un hook pasa por el modelo. El harness ejecuta el comando de shell directamente y no le pregunta a Claude si debería hacerlo.
Un hook recibe detalles sobre la llamada a herramienta completada como JSON, típicamente en stdin, incluyendo qué archivo fue tocado.
Debido a que el hook es determinista, es el lugar adecuado para cualquier cosa que deba suceder cada vez, sin excepciones y sin juicios.
Receta
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "npx prettier --write \"$CLAUDE_FILE_PATH\""
}
]
}
]
}
}Cuándo usar esto:
- Autoformatear un archivo en el instante en que se edita, para que el formateo nunca tenga que ser solicitado.
- Ejecutar un linter y mostrar advertencias justo después de un cambio, en lugar de esperar un paso de CI separado.
- Registrar cada archivo tocado durante una sesión para un rastro de auditoría.
- Iniciar un conjunto de pruebas rápido y específico para el archivo que acaba de cambiar.
- Regenerar un artefacto derivado (como una configuración compilada o un índice de tipos) después de que cambien sus fuentes.
Ejemplo de Trabajo
#!/usr/bin/env bash
# .claude/hooks/format-on-edit.sh
# Lee la carga útil del evento PostToolUse desde stdin y formatea el archivo tocado.
set -euo pipefail
payload="$(cat)"
file_path="$(echo "$payload" | jq -r '.tool_input.file_path // empty')"
if [ -z "$file_path" ]; then
exit 0
fi
case "$file_path" in
*.ts|*.tsx|*.js|*.jsx|*.json|*.css|*.md)
npx prettier --write "$file_path"
echo "Formatted: $file_path"
;;
*)
exit 0
;;
esac{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/format-on-edit.sh"
}
]
}
]
}
}Lo que esto demuestra:
- El campo
matcherlimita el hook a las llamadas a herramientasEdityWrite, por lo que nunca se dispara en, por ejemplo, un comandoBash. - El script lee la carga útil del evento desde stdin y extrae la ruta del archivo cambiado con
jq, en lugar de adivinar una variable de entorno. - Una declaración
caselimita el formateo a los tipos de archivo que Prettier entiende realmente, por lo que el hook no hace nada silenciosamente en los archivos que no debería tocar. - El script es un archivo separado y versionado, no una línea única en línea, lo que lo hace testeable y legible por sí solo.
set -euo pipefailhace que el script falle ruidosamente ante errores inesperados en lugar de hacer nada silenciosamente.
Análisis Profundo
Cómo Funciona
- Claude Code emite un evento
PostToolUseinmediatamente después de que una llamada a herramienta se completa exitosamente. - El harness verifica el
matcherde cada hookPostToolUseregistrado contra la herramienta que se acaba de ejecutar; solo se ejecutan los hooks coincidentes. - El
commandde cada hook coincidente se ejecuta como un comando de shell simple, con la carga útil del evento (nombre de la herramienta, entrada de la herramienta y resultado) disponible para él, típicamente a través de stdin como JSON. - El código de salida y la salida del hook se informan de vuelta a Claude Code; una salida no cero generalmente se presenta como un fallo, útil para hooks que también validan.
- Los hooks se ejecutan de forma síncrona con respecto a la finalización de la llamada a herramienta, pero no bloquean ni alteran la llamada a herramienta en sí, ya que la herramienta ya se ha completado cuando se dispara
PostToolUse. Ese comportamiento de bloqueo pertenece en cambio aPreToolUse.
Patrones de matcher
matcher | Coincide con |
|---|---|
Edit | Solo la herramienta Edit. |
Write | Solo la herramienta Write. |
Edit|Write | Tanto Edit como Write (alternancia de expresiones regulares). |
* o omitido | Cada llamada a herramienta, independientemente de qué herramienta se ejecutó. |
Notas sobre Scripts de Shell
# Prefiere leer entrada estructurada en lugar de depender de variables de entorno
# que pueden o no estar configuradas consistentemente entre implementaciones de hooks.
payload="$(cat)"
file_path="$(echo "$payload" | jq -r '.tool_input.file_path // empty')"
# Protege contra una ruta vacía antes de hacer cualquier trabajo.
[ -z "$file_path" ] && exit 0- Siempre protege contra una ruta de archivo faltante o vacía; no todas las cargas útiles de llamadas a herramientas tendrán la forma que un hook de formateo espera.
- Mantén el hook rápido. Se ejecuta en cada llamada a herramienta coincidente, por lo que un hook lento (un conjunto completo de pruebas, una compilación grande) ralentizará notablemente cada edición.
- Prefiere un archivo de script dedicado sobre un comando largo en línea en
settings.json, ya que es más fácil de probar, comparar y razonar de forma aislada.
Trampas Comunes
- Asumir que el hook puede bloquear o deshacer la edición -
PostToolUsese dispara después de que la llamada a herramienta ya se ha completado, por lo que el hook no puede evitar que la edición ocurra. Solución: usa un hookPreToolUseen su lugar si el objetivo es rechazar o bloquear una acción antes de que suceda. - Un hook lento hace que cada edición se sienta lenta - un hook que ejecuta un conjunto completo de pruebas o una compilación lenta en cada edición de archivo agrega esa latencia a cada edición, no solo a las significativas. Solución: limita el hook a operaciones rápidas, o reduce el
matchery la verificación del tipo de archivo para que solo se ejecute donde importa. - Codificar una ruta de archivo en lugar de leerla de la carga útil del evento - un hook escrito para un archivo específico se rompe en el momento en que necesita manejar un segundo archivo. Solución: siempre lee la ruta de archivo real de la carga útil de la llamada a herramienta, nunca asumas una ruta fija.
- Olvidar el
matchery dispararlo en cada llamada a herramienta - un hook sin alcance (sinmatcher, o*) se dispara incluso en herramientas no relacionadas comoBashoRead, desperdiciando ciclos y saturando la salida. Solución: establece unmatcherque nombre exactamente las herramientas que le importan al hook, comoEdit|Write. - Un hook que falla en archivos que no entiende - un formateador invocado contra un archivo binario o una extensión no compatible puede fallar y contaminar la sesión con ruido. Solución: agrega una guardia explícita de extensión de archivo (como la declaración
caseanterior) para que el hook salga silenciosamente en los archivos que no está destinado a tocar. - Tratar la salida del hook como invisible - la salida estándar y el código de salida del hook se muestran de nuevo en la sesión, por lo que un hook ruidoso o fallido agrega ruido (o errores aparentes) a cada edición. Solución: mantén la salida del hook mínima y solo usa una salida no cero en fallos genuinos que el usuario deba ver.
Alternativas
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
| Pedirle a Claude que formatee el archivo en el prompt | Una solicitud única, no algo que deba ocurrir en cada edición en el futuro. | El comportamiento debe ser incondicional y automático para todo el equipo, no depender de recordar pedirlo. |
Un hook PreToolUse | El objetivo es validar o bloquear la edición antes de que ocurra, no reaccionar después de que se complete. | La acción es un efecto secundario que debe ejecutarse después de una edición exitosa, como formatear o registrar. |
| Un paso de pipeline de CI | La verificación es costosa (conjunto completo de pruebas, compilación completa) y no necesita ejecutarse en cada edición local. | La retroalimentación rápida en cada edición es más importante que centralizar la verificación en CI. |
Preguntas Frecuentes
¿Puede un hook PostToolUse detener la edición a la que está reaccionando?
No. PostToolUse se dispara después de que la llamada a herramienta ya se ha completado, por lo que la edición ya ha ocurrido cuando se ejecuta el hook. El bloqueo pertenece en cambio a un hook PreToolUse.
¿Cómo sabe un hook PostToolUse qué archivo se editó recientemente?
La carga útil del evento incluye la entrada de la llamada a herramienta, típicamente entregada como JSON en stdin, que contiene la ruta del archivo para las llamadas a herramientas Edit y Write.
¿Contra qué coincide realmente el campo `matcher`?
- Coincide con el nombre de la herramienta que se acaba de ejecutar, como
EditoWrite. - La alternancia estilo regex (
Edit|Write) permite que una entrada de hook cubra múltiples herramientas. - Omitir el
matcher, o usar*, hace que el hook se dispare en cada llamada a herramienta.
¿Pueden ejecutarse múltiples hooks PostToolUse para el mismo evento?
Sí. Se pueden registrar múltiples entradas de hook bajo PostToolUse, y se ejecuta cada entrada cuyo matcher coincida con la herramienta completada.
¿Qué sucede si el script del hook sale con un estado no cero?
La salida no cero generalmente se presenta de nuevo en la sesión como una señal de fallo, lo cual es útil para hooks que realizan validaciones ligeras además de un efecto secundario como el formateo.
¿Debería un hook de formateo ejecutarse contra cada tipo de archivo?
No. Debería verificar la extensión (o ruta) del archivo y solo ejecutar el formateador contra tipos que realmente entienda, saliendo silenciosamente en cualquier otra cosa.
¿Es un hook PostToolUse el lugar adecuado para operaciones lentas como un conjunto completo de pruebas?
Generalmente no, ya que el hook se ejecuta en cada edición coincidente y un hook lento agrega esa latencia a cada edición. Las verificaciones lentas suelen ser mejores para CI o un comando activado manualmente.
¿Dónde se configuran los hooks PostToolUse?
En settings.json, bajo un array hooks.PostToolUse, donde cada entrada tiene un matcher y uno o más comandos hooks para ejecutar.
¿Se ejecuta el hook dentro del mismo proceso que Claude Code?
No. El command del hook se ejecuta como un comando de shell independiente, separado del proceso de razonamiento del modelo, y solo su salida y código de salida se informan de vuelta.
¿Puede un hook PostToolUse ver el resultado de la llamada a herramienta, no solo su entrada?
Sí. La carga útil del evento generalmente incluye tanto la entrada de la herramienta como su resultado, lo cual es útil para hooks que desean reaccionar de manera diferente dependiendo de lo que produjo la edición.
¿Es seguro confirmar un script de hook PostToolUse en un repositorio compartido?
Sí, y se recomienda, ya que un script de hook con ámbito de proyecto en control de versiones significa que cada compañero de equipo obtiene el mismo comportamiento automático de formateo o registro sin configuración individual.
¿Cuál es la diferencia entre un hook PostToolUse y un hook Stop?
Un hook PostToolUse se dispara después de que cada llamada a herramienta individual coincidente se completa, mientras que un hook Stop se dispara una vez, cuando finaliza la sesión o el turno, lo que lo hace más adecuado para resúmenes de fin de turno o limpieza en lugar de reacciones por edición.
Relacionados
- Comandos Personalizados, Hooks y Subagentes: Para Qué Sirve Cada Uno - cómo
PostToolUseencaja entre los tres puntos de extensión. - Bloquear Ediciones de Archivos Arriesgadas con un Hook PreToolUse - el hook que se ejecuta antes de la edición en lugar de después.
- Eventos Comunes de Hooks y Cuándo Usar Cada Uno - el catálogo completo de eventos del ciclo de vida a los que los hooks pueden adjuntarse.
- Mejores Prácticas para Comandos Personalizados, Hooks y Subagentes - convenciones para definir hooks de forma segura.
Versiones de Stack: Escrito contra la línea de modelos Claude actual a partir de ~junio de 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (el predeterminado) y Claude Haiku 4.5. Los nombres de modelos, precios y características del producto cambian rápidamente; verifique los detalles actuales en platform.claude.com/docs antes de confiar en ellos.