Un hook de Claude Code es un comando que Claude Code ejecuta en un punto fijo de su ciclo de vida, así que un aviso, un formateador o un bloqueo ocurren aunque el modelo no lo elija. La notificación documentada es un comando Notification en ~/.claude/settings.json: osascript en macOS, notify-send en Linux y un MessageBox de PowerShell en Windows. Confírmelo en el menú de solo lectura /hooks. Ese archivo es solo de esta máquina, y las sesiones en la nube no lo leen. Un hook compartido de Prettier va en el .claude/settings.json del proyecto, en PostToolUse, con el matcher Edit|Write. Una ejecución correcta no muestra nada en el chat y no ve un archivo que reescriba un comando Bash. Para detener una edición, el protect-files.sh de Anthropic escribe el motivo en stderr y sale con 2 antes de Edit o Write. Para detener un comando de shell destructivo, block-rm.sh sale con 0 e imprime permissionDecision deny. La salida 2 bloquea PreToolUse aunque el JSON diga allow. La salida 0 por sí sola no aprueba la llamada. PermissionRequest y Notification ignoran la salida 2. Un hook command que no puede arrancar, o un hook command, HTTP o de herramienta MCP que agota el tiempo en PreToolUse, deja la llamada a la herramienta en el flujo de permisos habitual, y el comando se ejecuta con los permisos de archivo de su cuenta de usuario.

Un hook se ejecuta aunque el modelo no lo elija

Un hook de Claude Code es un comando de shell unido a un momento del ciclo de vida de Claude Code. Claude Code ejecuta ese comando cada vez que llega el momento, así que la notificación, el formateador o el bloqueo ocurren aunque el modelo no elija ejecutarlo. La guía de hooks de Anthropic es el recetario. La referencia de hooks contiene los esquemas de eventos, la tabla de códigos de salida y los campos JSON.

Hay cinco tipos de manejador: command, http, mcp_tool, prompt y agent. Las tres recetas de abajo usan command. Los hooks prompt envían la entrada del hook a un modelo de Claude, Haiku de forma predeterminada en la guía, cuando la decisión exige juicio. Los hooks agent son experimentales, pueden cambiar, y la guía dice que los flujos de producción deben preferir los hooks command.

Anthropic también enlaza un validador de comandos Bash en GitHub como implementación de referencia completa.

Automatizar acciones con hooks

Referencia de hooks

Ejemplo de validador de comandos Bash

Empiece por la notificación de escritorio documentada

Abra ~/.claude/settings.json y añada un hook Notification. Cree el archivo si falta. El primer ejemplo de Anthropic es una notificación de pantalla con osascript en macOS, con el matcher vacío y “type”: “command”.

Si el archivo ya tiene una clave hooks, añada Notification junto a las otras claves de evento. Cada nombre de evento es una clave dentro de ese único objeto hooks. Sustituir el objeto elimina todos los hooks ya configurados en ese archivo.

Escriba /hooks para confirmar el registro. El menú lista cada evento y un recuento. Al seleccionar el hook se ven el matcher, el type, el archivo de origen y el comando. El menú es de solo lectura. Añadir, cambiar o quitar un hook significa editar el JSON, o pedirle a Claude que lo edite.

Para probarlo, pulse Esc y después Shift+Tab hasta que la barra de estado muestre manual mode on. Pida algo que requiera permiso y abandone la terminal. En macOS, osascript pasa por Script Editor. Si Script Editor no tiene permiso de notificación, el comando falla en silencio y macOS no le pedirá que lo conceda. Ejecute una vez osascript -e ‘display notification “test”’ y luego permita las notificaciones de Script Editor en System Settings.

~/.claude/settings.json

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

Linux y Windows usan el mismo evento Notification

El mismo hook Notification, el matcher vacío y el tipo command van en ~/.claude/settings.json en Linux y en Windows. Solo cambia la cadena del comando. El comando de Linux de Anthropic es notify-send. notify-send necesita un demonio de notificaciones de escritorio, que no tienen los servidores sin interfaz, las sesiones SSH ni la mayoría de los contenedores. Pruebe con notify-send ‘Claude Code’ ‘test’. Si falta el comando en Debian o Ubuntu, el paquete es libnotify-bin.

El comando de Windows de Anthropic abre un MessageBox de PowerShell. Es un diálogo, así que puede quedar detrás de la terminal. Pruebe primero el comando en PowerShell. Dentro de WSL, powershell.exe tiene que estar en PATH mediante la interoperabilidad de Windows. Un hook Notification no puede bloquear: se ignoran su código de salida y stderr.

Comando Notification de Anthropic para cada plataforma. Ponga el comando en el mismo campo command que el ejemplo de macOS.

| Plataforma | Comando |

| — | — |

| macOS | osascript -e ‘display notification “Claude Code needs your attention” with title “Claude Code”’ |

| Linux | notify-send ‘Claude Code’ ‘Claude Code needs your attention’ |

| Windows | powershell.exe -Command “[System.Reflection.Assembly]::LoadWithPartialName(‘System.Windows.Forms’); [System.Windows.Forms.MessageBox]::Show(‘Claude Code needs your attention’, ‘Claude Code’)” |

Filtre la notificación que realmente quiere

Un matcher vacío se dispara con todos los tipos de notificación. permission_prompt se dispara cuando Claude necesita aprobación para una herramienta o para la petición de red de un comando en sandbox. idle_prompt se dispara unos 60 segundos después de que Claude termina de responder si usted no ha escrito. Los diálogos de elicitation usan la misma puerta de unos seis segundos que permission_prompt.

En una terminal, el temporizador de permission_prompt empieza cuando aparece el aviso y espera unos seis segundos. Cada pulsación lo aplaza. En Claude Desktop, la extensión de VS Code y otros hosts que responden a las peticiones de permiso a través del Agent SDK, permission_prompt ocurre unos seis segundos después de la pregunta, y escribir no lo aplaza. Si responde antes, o un hook PermissionRequest responde antes, ese hook Notification no se ejecuta. Para el momento del permiso en sí, use PermissionRequest.

agent_needs_input se dispara cuando una sesión en segundo plano empieza a esperar su entrada con agent view abierto, o cuando la sesión actual hace la pregunta de configuración de terminal de un compañero y usted no ha escrito durante unos seis segundos. agent_completed se dispara cuando una sesión en segundo plano termina o falla, y solo mientras agent view está abierto. quota_auto_resume_fired se dispara cuando Claude Code continúa una tarea después de que un límite de uso de claude.ai la pausó, en el reinicio o antes si algo que usted hace durante la espera vuelve a dejar el uso disponible. quota_auto_resume_stale se dispara cuando ese límite se reinició mientras el ordenador durmió más de unos 30 minutos; Claude Code espera Enter. Tras un sueño más corto continúa y dispara quota_auto_resume_fired. quota_auto_resume_disabled se dispara cuando Claude Code termina la espera sin continuar la tarea. No se dispara si pulsa Esc o Ctrl+C, o si elige Don’t continue automatically.

agent_needs_input y agent_completed requieren Claude Code v2.1.198 o posterior. El caso de agent_needs_input de la configuración de terminal de un compañero requiere v2.1.248 o posterior. Los tres matchers quota_auto_resume requieren v2.1.234 o posterior. En una terminal, permission_prompt para la petición de red de un comando en sandbox requiere v2.1.246 o posterior.

El aviso personal va en el archivo de usuario y el hook compartido en el del proyecto

~/.claude/settings.json se aplica a todos los proyectos de esta máquina y no se puede compartir. .claude/settings.json se aplica a un proyecto y se puede confirmar en git. .claude/settings.local.json se aplica a un proyecto y queda en gitignore cuando Claude Code guarda ahí un ajuste. Las entradas de hooks se fusionan entre los ajustes de usuario, de proyecto y locales: cada archivo añade sus propios hooks. Un formateador del proyecto no quita un hook de notificación del usuario. El mismo manejador definido en más de un archivo de ajustes se ejecuta una vez. Una copia de ese manejador en un plugin o una skill permanece aparte. Sustituir el objeto hooks dentro de un archivo sigue eliminando los demás eventos configurados en ese archivo.

Las sesiones en la nube no leen su ~/.claude/settings.json local, así que un hook de notificación personal en ese archivo se queda en esta máquina.

Los hooks command se ejecutan con los permisos de su cuenta de usuario. Pueden modificar, borrar o acceder a cualquier archivo al que esa cuenta pueda acceder. Revise y pruebe un comando antes de confirmarlo en un archivo de proyecto compartido. En una sesión interactiva, Claude Code retiene los hooks de todos los archivos de ajustes, incluido ~/.claude/settings.json, hasta que se acepta el diálogo de confianza del espacio de trabajo. En una sesión claude -p o del SDK, Claude Code trata la carpeta como de confianza, así que se ejecutan los hooks confirmados en .claude/settings.json.

Dónde dice Anthropic que se aplica un ajuste de hook.

| Archivo | Alcance | Compartible |

| — | — | — |

| ~/.claude/settings.json | Todos los proyectos de esta máquina | No |

| .claude/settings.json | Un proyecto | Sí, se puede hacer commit |

| .claude/settings.local.json | Un proyecto | No. Queda en gitignore cuando Claude Code guarda ahí un ajuste |

Formatee un archivo después de Edit o Write

La receta de formatear al editar de Anthropic es un hook PostToolUse con el matcher Edit|Write en el .claude/settings.json del proyecto. El comando lee tool_input.file_path con jq y lo pasa a npx prettier –write. Añada PostToolUse junto a las claves de evento que ya estén en ese archivo.

Los ejemplos de Bash de la guía de hooks usan jq. Instálelo con brew install jq en macOS, o con apt-get install jq en Debian y Ubuntu. Cuando el hook tiene éxito, Claude Code no muestra nada en la conversación. La comprobación de Anthropic es pedirle a Claude que añada una línea de cadenas con comillas simples a un archivo JavaScript y luego abrir el archivo: con los ajustes predeterminados de Prettier, el hook las reescribe con comillas dobles.

PostToolUse se ejecuta después de que la herramienta ha tenido éxito, así que no puede deshacer la edición. Edit|Write también pasa por alto un archivo que reescribe un comando Bash. Anthropic señala un hook FileChanged cuando un archivo concreto debe reformatearse sea quien sea quien lo haya escrito. Un matcher FileChanged como .envrc|.env es una lista de nombres de archivo literales, dividida por |, y FileChanged no puede bloquear la escritura. Para cada cambio de un turno, la guía señala un barrido en Stop, o un matcher Bash|PowerShell cuyo script liste los archivos modificados y sin seguimiento con git status –porcelain. En Windows, donde la herramienta PowerShell es el shell principal, un hook que solo coincide con Bash no se dispara.

.claude/settings.json

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}

Bloquee una edición con el código de salida 2

El ejemplo protect-files de Anthropic es un hook PreToolUse sobre Edit|Write. El script lee tool_input.file_path, trata .env, package-lock.json y .git/ como subcadenas protegidas, escribe un mensaje Blocked en stderr y sale con 2. Claude Code bloquea entonces el Edit o el Write y pasa ese mensaje a Claude. La salida 0 al final del script informa de que no hay decisión, así que sigue aplicando el flujo de permisos habitual. No aprueba la edición.

En Windows, Write, Edit y Read entregan file_path con barras invertidas. La sustitución del script las convierte en barras inclinadas antes de comprobar el patrón. Una comparación con barras inclinadas contra una ruta con barras invertidas dejaría pasar la edición.

Guarde el script como .claude/hooks/protect-files.sh. En macOS y Linux, ejecute chmod +x .claude/hooks/protect-files.sh. Regístrelo desde el .claude/settings.json del proyecto con la ruta entre comillas, porque este comando es shell form. La prueba de Anthropic es pedirle a Claude que añada un comentario a .env. La edición se bloquea antes de ejecutarse.

La salida 2 en PreToolUse sigue bloqueando si el script también imprime JSON que dice allow. Use stderr más salida 2, o salida 0 más JSON, y elija un estilo por hook.

.claude/hooks/protect-files.sh

#!/bin/bash
# protect-files.sh

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Normalize Windows backslash separators so the patterns below match
FILE_PATH="${FILE_PATH//\\//}"

PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/")

for pattern in "${PROTECTED_PATTERNS[@]}"; do
  if [[ "$FILE_PATH" == *"$pattern"* ]]; then
    echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
    exit 2
  fi
done

exit 0

Registre la protección de archivos antes de Edit o Write

Añada este grupo PreToolUse al .claude/settings.json del proyecto, junto a las claves de evento que ya estén. $CLAUDE_PROJECT_DIR es la raíz del proyecto donde empezó la sesión. Las comillas importan en shell form para que sobreviva una ruta con espacios.

Un hook command que no puede arrancar, un archivo ausente o un script que no es ejecutable, no bloquea la edición. El shell sale con un código como 127, la transcripción muestra un error de hook que no bloquea y la llamada a la herramienta sigue. Un hook command, HTTP o de herramienta MCP que alcanza su tiempo de espera en PreToolUse también deja la llamada en el flujo de permisos habitual. Esos hooks tienen un valor predeterminado de 600 segundos. Un bloqueo firme sigue perteneciendo al sistema de permisos, porque una guarda que no arranca o agota el tiempo deja la puerta abierta. La excepción que señala la referencia es un hook de callback del Agent SDK que supera su tiempo de espera en PreToolUse: ese callback bloquea la llamada a la herramienta.

.claude/settings.json

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
          }
        ]
      }
    ]
  }
}

Deniegue un comando de shell destructivo con JSON

Para detener un comando antes de que se ejecute, use PreToolUse sobre Bash. El ejemplo de la referencia de Anthropic guarda block-rm.sh en el proyecto, lo hace ejecutable con chmod +x y lo registra con una regla if de Bash(rm *). El script imprime un permissionDecision deny cuando el comando contiene rm -rf. En caso contrario sale con 0, lo que deja en su sitio el flujo de permisos habitual y no aprueba el comando.

El campo if contiene una regla de permiso y es best-effort. Cuando Claude Code no puede saber qué comandos ejecuta una entrada Bash, ejecuta el hook con independencia del patrón. La referencia dice que, para un allow o un deny firme, se use el sistema de permisos y no un hook. Un permissionDecision deny que el hook sí devuelve sigue bloqueando la herramienta en el modo bypassPermissions y con –dangerously-skip-permissions. Una decisión allow no afloja las reglas deny de los ajustes.

En PreToolUse, deny cancela la llamada y envía permissionDecisionReason a Claude, ask muestra el aviso habitual y allow se salta el aviso interactivo mientras siguen aplicando las reglas deny y ask. defer existe para ejecuciones no interactivas de claude -p. Los hooks PreToolUse hermanos siguen ejecutándose después de que uno devuelva deny. Las decisiones fusionadas usan el orden deny, defer, ask, allow.

Este registro pone args en un array vacío, que es exec form: Claude Code lanza el script directamente, sin shell. Omita args cuando el comando necesita una tubería o &&, como hace el hook de Prettier. En Windows, donde PowerShell es el shell principal, coincida también con Bash|PowerShell. Un hook solo de Bash no se dispara allí. La referencia incluye un manejador de PowerShell aparte para Remove-Item.

.claude/settings.json

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(rm *)",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh",
            "args": []
          }
        ]
      }
    ]
  }
}

El script que imprime deny

Guárdelo como .claude/hooks/block-rm.sh y ejecute chmod +x en macOS y Linux. Usa jq, que tiene que estar en PATH. Para rm -rf imprime JSON y sale con 0, así que es el JSON lo que deniega la llamada. Para un comando más seguro, como rm file.txt, sale con 0 y sin salida. Mantenga la salida 0 en este estilo. Añadir la salida 2 bloquearía aunque una edición posterior del JSON dijera allow.

PreToolUse también se salta EndConversation y no ve los archivos que usted referencia con @. Claude Code inserta esos contenidos al construir el prompt, así que un hook Read no se dispara para ellos.

.claude/hooks/block-rm.sh

#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')

if echo "$COMMAND" | grep -q 'rm -rf'; then
  jq -n '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: "Destructive command blocked by hook"
    }
  }'
else
  exit 0
fi

Los códigos de salida cambian según el evento

La salida 0 significa que el hook no plantea objeción mediante su código de salida. En PreToolUse eso deja en su sitio el flujo de permisos habitual. La salida 0 es también la salida que hay que usar cuando el hook imprime una decisión JSON. La salida 2 bloquea los eventos que pueden bloquearse, y el motivo es el reason de la decisión JSON cuando esa decisión bloquea; si no, stderr. PermissionRequest ignora la salida 2; un deny tiene que venir de hookSpecificOutput.decision.behavior. Un hook Notification ignora su código de salida y stderr. PostToolUse no puede deshacer una herramienta que ya se ejecutó; la salida 2 allí muestra stderr a Claude después del hecho.

Para la mayoría de los eventos, la salida 2 es el único código de salida que bloquea por sí solo. Sin un JSON de decisión válido, la salida 1 es un error que no bloquea y la acción sigue. WorktreeCreate es una excepción: cualquier salida distinta de cero hace fallar la creación del worktree. Si mezcla la salida 2 con JSON en un evento que puede bloquearse, la salida 2 sigue bloqueando, y un permissionDecision allow no puede anularla. Antes de Claude Code v2.1.214, la salida 2 combinada con JSON en stdout que no superaba la validación del esquema no bloqueaba. La referencia actual dice que esa combinación sigue bloqueando, y Claude Code usa stderr como motivo.

Si un perfil de shell imprime texto antes del JSON, stdout ya no empieza por {, Claude Code lo trata como texto plano y, con salida 0, la decisión se ignora. La corrección de la guía es hacer echo solo en shells interactivos. Un hook command que no puede arrancar, y un hook command, HTTP o de herramienta MCP que agota el tiempo en PreToolUse, deja la llamada a la herramienta en el flujo de permisos habitual.

  • La salida 0 en PreToolUse deja en su sitio el flujo de permisos habitual.
  • La salida 0 con JSON es control estructurado: permissionDecision en PreToolUse, decision.behavior en PermissionRequest, o decision block de nivel superior en eventos como Stop y PostToolUse.
  • La salida 2 bloquea PreToolUse aunque el JSON de stdout diga allow.
  • La salida 2 en PostToolUse muestra stderr a Claude después de que la herramienta ya se ha ejecutado.
  • PermissionRequest y Notification ignoran la salida 2.
  • La salida 1 sin un JSON de decisión válido no bloquea.

Qué dice la referencia de hooks de Anthropic que hacen la salida 2 y el JSON en los eventos de estas recetas.

| Evento | Qué hace la salida 2 | Campo JSON que decide |

| — | — | — |

| PreToolUse | Bloquea la llamada a la herramienta | hookSpecificOutput.permissionDecision: allow, deny, ask o defer |

| PostToolUse | Muestra stderr a Claude; la herramienta ya se ha ejecutado | decision de nivel superior establecido en block |

| PermissionRequest | Se ignora; el flujo de permisos continúa | hookSpecificOutput.decision.behavior: allow o deny |

| Notification | Se ignora | Ninguno. Este evento no tiene control de decisión |

| Stop | Mantiene a Claude trabajando en lugar de terminar el turno | decision de nivel superior establecido en block |

Tres cadencias cubren la mayor parte de una sesión

La referencia de hooks agrupa los eventos habituales en tres cadencias. SessionStart y SessionEnd se ejecutan por sesión. UserPromptSubmit, Stop y StopFailure se ejecutan por turno. PreToolUse y PostToolUse se ejecutan en las llamadas a herramientas dentro del bucle agéntico, excepto EndConversation, que se salta ambos. Un formateador va en PostToolUse. Un bloqueo va en PreToolUse. Un aviso de escritorio va en Notification, que se dispara cuando Claude Code envía una notificación, incluido el permission_prompt demorado descrito arriba.

Los hooks que coinciden para el mismo evento se ejecutan hasta el final en paralelo. Un deny no detiene un registrador hermano. Cuando varios hooks PreToolUse devuelven updatedInput para la misma herramienta, surte efecto el último que termina, y ese orden no es determinista.

Añada el comando Notification de Anthropic a ~/.claude/settings.json y confírmelo en /hooks. En Linux use notify-send; en Windows, el comando MessageBox de PowerShell. Instale jq y añada el comando Prettier de PostToolUse con Edit|Write al .claude/settings.json del proyecto. Guarde protect-files.sh, ejecute chmod +x en macOS o Linux y regístrelo en PreToolUse con la ruta $CLAUDE_PROJECT_DIR entre comillas. Guarde block-rm.sh del mismo modo y regístrelo con if en Bash(rm *) y args en un array vacío para que imprima permissionDecision deny. Mantenga también un deny firme en el sistema de permisos, porque un script de guarda que no arranca o agota el tiempo no bloquea la llamada a la herramienta.

Fuentes