Saltar al contenido
NEWS
Guía de uso

Cómo usar los hooks de Claude Code: 5 formas y 8 que no sirven

Unos 27 min de lectura
Cómo usar los hooks de Claude Code: 5 formas y 8 que no sirven
Puntos clave

Los hooks de Claude Code son un mecanismo que ejecuta tu propio comando sin falta en un momento determinado. AInformation escribió cinco tipos en una carpeta de prueba y los ejecutó tres veces cada uno, con y sin hooks. Aquí mostramos, con ejemplos reales, cómo detienen, agregan o devuelven acciones, y qué formas de escritura no funcionaron.

SHARE
Avatar de Shunta Fujii

Consultor de adopción de IA

Shunta Fujii

Especialista en IA que sigue siempre de cerca las últimas novedades de la IA y se mantiene al día. También trabaja como consultor de implementación de IA, sobre todo en mejorar la eficiencia del trabajo, aumentar la productividad y desarrollar nuevos negocios con IA generativa.
AInformation es un medio de datos sobre IA que investiga y cuenta por sí mismo los precios de herramientas de IA y las ofertas de empleo. Ofrece cifras útiles tanto para elegir una IA de forma personal como para implementarla en una empresa. Shunta Fujii gestiona el sitio y revisa los artículos por su cuenta. Perfil de Shunta Fujii

...Leer más

Los hooks de Claude Code son un mecanismo que ejecuta tu propio comando sin falta en un momento determinado. AInformation escribió cinco tipos en una carpeta de prueba y los ejecutó tres veces, con y sin hooks. Aquí mostramos, con ejemplos reales, cómo detienen, agregan o devuelven acciones, y qué formas de escritura no funcionaron.

Para usar los hooks de Claude Code basta con escribir tres cosas en «hooks» dentro de settings.json: cuándo, con qué herramienta y qué ejecutar. AInformation escribió y ejecutó cinco tipos de hooks el 3 de noviembre de 2026 en una carpeta de prueba con Claude Code 2.1.317. Al enviar la misma solicitud tres veces con hooks y tres veces sin hooks, la acción definida se cumplió siempre, y solo, en las ejecuciones con hooks.

AInformation ejecuta normalmente 54 tareas programadas creadas con Claude Code todos los días. En su configuración personal tiene incorporados 12 hooks, que también usa para revertir el trabajo cuando se olvida escribir el registro. Los hooks de este artículo se escribieron de forma nueva, solo como práctica, de modo que se pueda mostrar todo su contenido. La forma de instalar Claude Code en sí está explicada en el artículo sobre cómo usar Claude Code.

Lo que vas a aprender en este artículo
  • La forma de escribir hooks en settings.json y el texto completo de cinco tipos listos para usar (48 líneas en total)
  • Al entregar primero la nota de la vez anterior, respondió la continuación sin usar herramientas en 1.6–3 segundos (sin hooks tardó 16.8–22 segundos)
  • El hook que detiene rm cambió la acción a mover a trash las tres veces, y el hook Stop hizo que se escribiera un informe las tres veces
  • Cuando se le dijo que la prueba había fallado, Claude reescribió la prueba en lugar del código. Cómo evitarlo
  • Formas de escritura que no funcionaron, como un matcher en minúsculas o exit 1, y cómo corregirlas
  • Se creó un Mod que muestra una franja encima del prompt, y también se agregó un comando de barra (slash command)

¿Qué son los hooks de Claude Code? Comandos que se ejecutan sin falta en un momento determinado

Los hooks son un mecanismo que hace que Claude Code ejecute tu propio comando en un momento fijo. Las reglas escritas en CLAUDE.md o en un skill son peticiones que la IA lee y, en principio, cumple. Pero cuando está ocupada, puede no cumplirlas. Los hooks funcionan porque el propio Claude Code ejecuta el comando, así que se ejecutan siempre, sin importar el “estado de ánimo” de la IA.

Los 5 momentos en los que se activan los hooks. Al iniciar. Entregar primero la nota de la vez anterior. Al hacer una solicitud. Detener una petición con una clave sensible. Antes y después de usar una herramienta. Detener rm y ejecutar la prueba. Justo antes de terminar. Devolver la tarea si no hay informe. Los hooks se ejecutan sin falta en el momento definido. Ilustración creada por AInformation (viñetas de pasos en fila horizontal)
Los 5 momentos en los que se activan los hooks

Los momentos de activación están divididos con mucho detalle. Según la documentación oficial, al 3 de noviembre de 2026 existían 33 tipos. Los cinco más usados son los siguientes. En esta prueba se escribió uno de cada uno de estos cinco.

Los 5 tipos de hooks más usados (la documentación oficial enumera 33 en total)

Tipo Momento de activación Qué puede detener Prueba de esta vez
SessionStart Al iniciar (no detiene nada) Entregar la nota de la vez anterior
UserPromptSubmit Al enviar una solicitud Esa solicitud Detener una clave y agregar reglas
PreToolUse Antes de usar una herramienta Esa llamada a la herramienta Detener rm
PostToolUse Después de usar una herramienta (ya se ejecutó) Ejecutar la prueba
Stop Antes de terminar la respuesta El hecho de terminar Devolver la tarea si no hay informe
Datos: documentación oficial de Claude Code (Hooks reference, guardada el 3 de noviembre de 2026) y pruebas de AInformation / Elaborado por AInformation

Los skills y el MCP también son mecanismos que funcionan de forma automática, pero cumplen funciones distintas. Un skill hace que la IA lea un manual de pasos, y el MCP conecta herramientas externas. Esta diferencia de uso está explicada en el artículo sobre cómo crear Claude Skills y en el artículo sobre cómo usar MCP. Los hooks se usan para lo que “sin falta se debe detener” o “sin falta se debe agregar”.

Cómo escribir settings.json (texto completo de la prueba)

Los hooks se escriben en settings.json. Existen tres archivos posibles, y el alcance de aplicación cambia según dónde se escriban. En esta ocasión, para no modificar la configuración habitual de AInformation, se escribió únicamente en .claude/settings.json dentro de la carpeta de prueba.

Dónde escribir los hooks (existen 3 archivos settings.json)

Archivo Alcance de aplicación Uso recomendado
~/.claude/settings.json Todas tus carpetas Reglas que quieres mantener siempre
.claude/settings.json Esa carpeta (compartido por Git) Reglas compartidas con el equipo (esta vez)
.claude/settings.local.json Solo para ti, en esa carpeta Pruebas personales
Datos: documentación oficial de Claude Code (Settings, guardada el 3 de noviembre de 2026) / Elaborado por AInformation

A continuación está el texto completo del settings.json usado en esta prueba. Para cada tipo se coloca un conjunto de «matcher» y «hooks» dentro de [ ]. El matcher es el nombre de la herramienta, y se escribe como Bash o Write|Edit.

{
  "hooks": {
    "SessionStart": [
      { "hooks": [ { "type": "command", "command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/session_start.py" } ] }
    ],
    "UserPromptSubmit": [
      { "hooks": [ { "type": "command", "command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/prompt_guard.py" } ] }
    ],
    "PreToolUse": [
      { "matcher": "Bash", "hooks": [ { "type": "command", "command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/bash_guard.py" } ] }
    ],
    "PostToolUse": [
      { "matcher": "Write|Edit", "hooks": [ { "type": "command", "command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/after_write.py" } ] }
    ],
    "Stop": [
      { "hooks": [ { "type": "command", "command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/stop_check.py" } ] }
    ]
  }
}

El $CLAUDE_PROJECT_DIR al inicio de command es un nombre fijo que se reemplaza por la ubicación de la carpeta del proyecto. Si se escribe sin esto, se produce un error que se detalla más adelante. El contenido de cada hook es un script pequeño en Python, de 7 a 11 líneas.

Al hook se le entrega en formato JSON la información de lo que ocurrió. A continuación se muestra el contenido real recibido antes de usar una herramienta (PreToolUse). Solo se ocultaron los números y las rutas.

{
 "session_id": "…(número distinto cada vez)",
 "transcript_path": "~/.claude/projects/…/número_de_conversación.jsonl",
 "cwd": "/private/tmp/hk/tui_all",
 "permission_mode": "acceptEdits",
 "hook_event_name": "PreToolUse",
 "tool_name": "Bash",
 "tool_input": {
  "command": "ls -la memo/ && head -c 2000 memo/kari.txt",
  "description": "Inspect the memo file before deleting"
 },
 "tool_use_id": "…(número distinto cada vez)"
}

El hook lee esto y devuelve si se permite continuar o si se detiene. La forma de responder se determina mediante el código de salida y el texto que se muestra en pantalla.

Cómo responde el hook y qué pasó esta vez

Forma de respuesta Qué ocurre Resultado de esta prueba
Termina en 0 y no muestra nada Continúa tal cual Todo pasó excepto rm
Termina en 0 y muestra un JSON Detiene o agrega algo según el contenido del JSON 3 hooks lograron detenerlo
Termina en 2 y muestra el motivo Se detiene y el motivo se pasa a Claude rm se detuvo las 3 veces
Termina en otro código, como 1 Solo avisa, sin detener rm pasó sin problema
Datos: investigación de AInformation (medición real del 3 de noviembre de 2026, ejecutando Claude Code 2.1.317 (Opus 5.5) en una carpeta de prueba de Mac). Las reglas de respuesta provienen de la documentación oficial (Hooks reference) / Elaborado por AInformation

La documentación oficial dice: «Exit with code 2 to block the action». Para detener una acción, la forma más breve es terminar con el código 2 y mostrar el motivo en la salida de error estándar. Si quieres definir algo con más detalle, puedes terminar con 0 y mostrar un solo JSON.

Una vez escrito, inicia claude en la carpeta. La primera vez que abres una carpeta aparece una confirmación para saber si confías en ella, y los hooks del proyecto no funcionan hasta que eliges Yes. Después de iniciar, al escribir /hooks aparece la lista de hooks cargados. En la versión 2.1.317 apareció el mensaje «This menu is read-only», así que no se pueden agregar hooks desde esa lista. Para agregarlos, hay que editar settings.json directamente o pedírselo a Claude.

Pantalla de terminal en negro. Debajo del título Hooks aparece '5 hooks on 5 events' y 'This menu is read-only', con cinco hooks listados desde PreToolUse hasta Stop
Lista de /hooks (solo lectura, con los 5 hooks ordenados por tipo)

Se escribieron los 5 tipos y se ejecutaron 3 veces cada uno, con y sin hook

Antes de nada, se unificó la forma de la prueba. En la carpeta de prueba se colocó un CSV de ventas (30 filas), un pequeño programa de cálculo y una nota de trabajo. Se volvió a crear la carpeta cada vez, y se ejecutó 3 veces con un solo tipo de hook activado y 3 veces sin ningún hook.

Se usó Claude Code 2.1.317 (Opus 5.5) con la opción -p, que no muestra pantalla. La versión 2.1.317 se publicó el 3 de noviembre de 2026, y la verificación se hizo ese mismo día. Como la configuración personal de AInformation también incluye hooks, se usó –setting-sources project para que solo se leyera la configuración de la carpeta de prueba. Los segundos indicados son los que reporta Claude Code.

Resultado de ejecutar los 5 tipos de hook 3 veces, con y sin hook

Tipo Solicitud Con hook Sin hook
SessionStart Cuéntame cómo quedó la última vez 1.6–3 segundos, 0 usos de herramientas 16.8–22 segundos, 3–7 usos de herramientas
UserPromptSubmit ¿Cuál es el total de ventas de septiembre? Incluyó el monto con impuestos las 3 veces Incluyó el monto con impuestos 1 vez
PreToolUse Borra kari.txt Las 3 veces fue a la papelera Las 3 veces se eliminó
PostToolUse Agrega una función para el monto con impuestos Se ejecutaron las pruebas las 3 veces Las pruebas no se ejecutaron
Stop Suma los totales por mes Hubo informe las 3 veces 0 informes
Datos: investigación de AInformation (medición real del 3 de noviembre de 2026, ejecutando Claude Code 2.1.317 (Opus 5.5) en una carpeta de prueba de Mac). Con -p, 3 veces por cada tipo; los segundos son el duration_ms que reporta Claude Code / Elaborado por AInformation

En los 5 tipos, el comportamiento definido ocurrió las 3 veces en las rondas con hook. El tiempo que consume cada hook es pequeño: al medir 20 veces cada uno, el promedio fue de unos 20 milisegundos por hook. Solo el hook que ejecuta las pruebas tomó 47 milisegundos. A partir de aquí se revisa cada tipo por separado, con su contenido y resultado.

SessionStart: entregar la nota anterior desde el inicio

Es un hook que se activa al iniciar la sesión. Lee las últimas 2 líneas de la nota de trabajo y se las entrega a Claude de antemano. El texto que se muestra en la salida estándar se incorpora directamente al inicio de la conversación, pero aquí se entregó mediante additionalContext dentro del JSON.

# SessionStart: entrega a Claude, desde el inicio, las últimas 2 líneas de la nota de trabajo anterior
import json, os, sys
data = json.load(sys.stdin)
memo = os.path.join(os.environ["CLAUDE_PROJECT_DIR"], "nota_de_trabajo.txt")
last = open(memo, encoding="utf-8").read().strip().splitlines()[-2:]
print(json.dumps({"hookSpecificOutput": {"hookEventName": "SessionStart",
      "additionalContext": "Nota de trabajo anterior (últimas 2 líneas):\n" + "\n".join(last)}}, ensure_ascii=False))

Se le pidió: «Dime en dos líneas hasta dónde se avanzó la última vez y qué hay que hacer a continuación». En las rondas con hook, no se usó ninguna herramienta y respondió en 1.6–3 segundos. En las rondas sin hook, se buscó por toda la carpeta y se usaron herramientas entre 3 y 7 veces. El tiempo hasta la respuesta fue de 16.8–22 segundos. El contenido de la respuesta fue correcto en las 6 ocasiones.

La cantidad de salida también fue distinta. Con hook fue de 57 a 103 tokens, y sin hook, de 668 a 915 tokens. Si quieres que se lea siempre lo mismo, entregarlo mediante el hook de inicio resulta más rápido y económico.

UserPromptSubmit: detener solicitudes con claves y agregar una regla de una línea

Es un hook que se activa en el instante en que se envía la solicitud. Esta vez se le hizo hacer dos cosas. Si la solicitud contiene una cadena parecida a una clave (una cadena larga que empieza con sk-), se detiene. En el resto de las solicitudes, agrega una línea con la regla de «incluir también el monto con impuestos».

# UserPromptSubmit: no permite enviar solicitudes que contengan una cadena parecida a una clave. En el resto, agrega una regla de una línea
import json, re, sys
data = json.load(sys.stdin)
if re.search(r"sk-[A-Za-z0-9]{16,}", data.get("prompt", "")):
    print(json.dumps({"decision": "block", "reason": "Contiene una cadena parecida a una clave (sk-...). Elimínala y vuelve a enviar."}, ensure_ascii=False))
    sys.exit(0)
print("Los montos de esta carpeta no incluyen impuestos. Al responder un monto, incluye también el monto con impuestos (10 %, redondeando hacia abajo fracciones menores a 1 yen).")

Las solicitudes que contenían una clave se detuvieron las 3 veces, en 0.1 a 0.1 segundos. No se envió nada a la IA, y la salida también fue de 0 tokens. En la pantalla de conversación apareció el motivo de la detención, y la solicitud original volvió al campo de entrada. Así se puede borrar la clave y volver a enviarla.

Pantalla de terminal. Debajo de 'UserPromptSubmit operation blocked by hook' aparece el motivo 'Contiene una cadena parecida a una clave', y la solicitud original vuelve al campo de entrada
UserPromptSubmit detuvo una solicitud que contenía una clave

También preguntamos: “¿Cuál es el total de ventas de septiembre en memo/uriage.csv?”. La respuesta fue correcta las 6 veces: ¥20,460 (JPY) sin impuestos. Solo con el hook escribió también el monto con impuestos, ¥22,506 (JPY), en 3 de las veces. Sin el hook, solo lo hizo una vez. Si quieres que agregue siempre ese dato adicional, basta con incluirlo en el hook de la solicitud para que no se olvide.

PreToolUse: detener rm y hacer que mueva los archivos a trash

Este hook se ejecuta justo antes de que se use una herramienta. Si el comando de Bash contiene rm, el hook termina con el código 2 y devuelve el motivo.

# PreToolUse (Bash): detener rm e indicar que se mueva a trash
import json, re, sys
data = json.load(sys.stdin)
cmd = data.get("tool_input", {}).get("command", "")
if re.search(r"(^|[;&|]\s*|\s)rm\s", " " + cmd):
    print("No se puede usar rm. Mueve el archivo que quieres eliminar a trash/ con mv.", file=sys.stderr)
    sys.exit(2)

Le pedimos: “El archivo memo/kari.txt ya no es necesario, bórralo”. La configuración de permisos permite rm, pero el hook actúa antes, así que puede detenerlo. En las 3 veces que se usó el hook, rm fue bloqueado y Claude leyó el motivo y movió el archivo a trash. En su propia respuesta, incluso aclaró: “No lo he eliminado por completo”.

Pantalla de la terminal. Debajo de Bash(rm memo/kari.txt) aparece en rojo PreToolUse:Bash hook error con el motivo 'No se puede usar rm', y en la línea siguiente se mueve el archivo a trash con mv
PreToolUse detuvo rm y Claude movió el archivo a trash

Sin el hook, las 3 veces el archivo se eliminó con rm. El tiempo que tomó fue de 7.8 a 12.6 segundos con el hook, y de 5.2 a 7.1 segundos sin él. Detener la acción añade algo de tiempo. Para operaciones irreversibles como eliminar, enviar o publicar en producción, usar este hook da tranquilidad.

PostToolUse: ejecutar las pruebas después de escribir

Este hook se ejecuta justo después de usar una herramienta. Si se escribe un archivo .py, ejecuta las pruebas y solo devuelve el motivo a Claude si fallan.

# PostToolUse (Write, Edit): al escribir un .py, ejecutar las pruebas y devolver el motivo a Claude si fallan
import json, os, subprocess, sys
data = json.load(sys.stdin)
path = data.get("tool_input", {}).get("file_path", "")
if not path.endswith(".py"):
    sys.exit(0)
root = os.environ["CLAUDE_PROJECT_DIR"]
r = subprocess.run([sys.executable, os.path.join(root, "app", "test_calc.py")], capture_output=True, text=True)
ok = r.returncode == 0
if not ok:
    print(json.dumps({"decision": "block", "reason": "app/test_calc.py falló:\n" + (r.stderr or r.stdout)[-600:]}, ensure_ascii=False))

Le pedimos: “Agrega a app/calc.py una función zeikomi(n) que convierta un monto sin impuestos a uno con impuestos. La tasa es del 10 %”. En las veces que se usó el hook, las pruebas se ejecutaron siempre justo después de escribir el código: 3 de 3 veces. En esta ocasión, en las 6 veces Claude leyó primero las pruebas e incluso ajustó el redondeo hacia abajo por debajo de un yen. Ninguna vez fallaron las pruebas.

Probamos a propósito el comportamiento cuando las pruebas fallan, en la pantalla de conversación. Como se describe más adelante, Claude terminó reescribiendo la prueba en lugar del código. Como PostToolUse se ejecuta después de escribir, no puede detener el contenido ya escrito. Si quieres detenerlo antes, debes usar PreToolUse.

Stop: devolver la tarea si falta el informe

Este hook se ejecuta justo antes de que Claude termine su respuesta. Si report.md no tiene la línea “Confirmación: completada”, no deja que termine y devuelve el motivo.

# Stop: si report.md no tiene 'Confirmación: completada', devolver la tarea en lugar de terminar
import json, os, sys
data = json.load(sys.stdin)
rep = os.path.join(os.environ["CLAUDE_PROJECT_DIR"], "report.md")
done = os.path.exists(rep) and "Confirmación: completada" in open(rep, encoding="utf-8").read()
if not done and not data.get("stop_hook_active"):
    print(json.dumps({"decision": "block", "reason": "Escribe en report.md lo que hiciste y una línea con 'Confirmación: completada' antes de terminar."}, ensure_ascii=False))
    sys.exit(0)

Le pedimos: “Suma las ventas de memo/uriage.csv por mes y escribe el resultado en memo/matome.csv”. La solicitud no menciona nada sobre un informe. En las 3 veces que se usó el hook, la tarea se devolvió una vez en cada ocasión, y después de eso Claude escribió report.md y terminó. Sin el hook, Claude solo escribió el archivo con los totales y terminó (0 informes).

Pantalla de la terminal. Aparece 'Stop hook error' con el motivo de escribir en report.md una línea con 'Confirmación: completada' antes de terminar, y debajo el rastro de Write(report.md) con 5 líneas escritas
El hook Stop devolvió la tarea y Claude escribió report.md

Al devolver la tarea, el tiempo se extendió: de 19.7 a 32.2 segundos con el hook, y de 8.9 a 15 segundos sin él. Lo mismo ocurrió en la pantalla de conversación: desde que se detuvo rm hasta que se terminó de escribir el informe pasaron 13 segundos.

Precauciones al devolver tareas con el hook Stop (se interrumpió tras 8 veces)

El hook Stop es una herramienta poderosa. Cada vez que devuelve un motivo, Claude sigue trabajando, así que si la condición nunca se cumple, el proceso no se detiene nunca. Para eso se usa stop_hook_active. La documentación oficial dice: “The stop_hook_active field is true when Claude Code is already continuing as a result of a stop hook”. El hook anterior está diseñado para que, si este valor es true, deje pasar la segunda vez.

También probamos una versión sin esta verificación: un hook que siempre responde “Todavía no termines”. Al preguntar el número de filas del CSV, Claude repitió la misma respuesta mientras recibía 10 devoluciones seguidas. Al final, Claude Code interrumpió el proceso y la respuesta de -p volvió vacía.

Existe un límite en el número de repeticiones. La documentación oficial indica: “Claude Code applies an 8-consecutive-continuation cap: after stop hooks have continued the turn eight times in a row, Claude Code overrides the next block and ends the turn”. Además: “The count of consecutive continuations resets each time Claude calls a tool”. En nuestro caso, como se usó Read antes de la segunda vez, el cálculo indica que se interrumpió en la novena vez posterior. Coincidió exactamente con lo que dice la documentación.

En los hooks que devuelven la tarea, conviene escribir en el motivo qué hay que hacer de forma concreta. Por ejemplo, escribir algo como “Escribe en report.md lo que hiciste y una línea de confirmación” resolvió el problema en un solo intento. Con motivos vagos como “Verifica de nuevo”, Claude solo repetía lo mismo.

Cuando PostToolUse detectó que una prueba fallaba, Claude reescribió la prueba

En la pantalla de conversación, pedimos: “Cambia el cálculo del monto con impuestos para que redondee las fracciones de menos de un yen”. Como la prueba asume el redondeo hacia abajo, justo después del cambio el hook devolvió “La prueba falló”.

Pantalla de la terminal. Debajo del cambio en calc.py hacia el redondeo aparece 'PostToolUse:Edit hook returned blocking error' y 'AssertionError: 1099'
La prueba falló justo después de escribir el código, y PostToolUse devolvió el motivo

Entonces Claude interpretó que lo que se le pedía era redondear, y reescribió el valor esperado por la prueba de 1,098 a 1,099. Además, añadió una prueba más para los ¥5 (JPY) y logró que todas las pruebas pasaran. Esto coincide con la intención de quien hizo la petición, pero desaparece la regla que la prueba quería proteger.

Pantalla de la terminal. En Update(test_calc.py) se reescribe el valor esperado de 999 de 1098 a 1099, se añade la línea de los 5 yenes y aparece test ok
Claude optó por reescribir el valor esperado de la prueba

Para evitarlo, hay que detener la escritura en el archivo de prueba antes de que ocurra. Añadimos el siguiente hook al PreToolUse de settings.json mientras la sesión seguía abierta.

# PreToolUse (Write, Edit): no permitir que se reescriban los archivos de prueba
import json, os, sys
data = json.load(sys.stdin)
path = data.get("tool_input", {}).get("file_path", "")
if os.path.basename(path).startswith("test_"):
    print(json.dumps({"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny",
          "permissionDecisionReason": "No reescribas los archivos de prueba. Corrige el código para que cumpla con la prueba."}}, ensure_ascii=False))
    sys.exit(0)

Después de añadirlo, sin reiniciar, le pedimos que volviera a poner la línea del 999 en 1098, el valor truncado. El hook actuó de inmediato, detuvo la reescritura de la prueba, y Claude propuso tres formas de proceder para consultarlo con nosotros. La documentación oficial también indica: «Direct edits to hooks in settings files are normally picked up automatically by the file watcher».

Pantalla de la terminal. Debajo de Update(test_calc.py) aparece PreToolUse:Edit hook error junto con el motivo 'no reescribas los archivos de prueba', y Claude responde que no pudo hacer la reescritura
El hook añadido mientras la sesión seguía abierta detuvo la reescritura de la prueba

Sin embargo, este hook solo vigila Write y Edit. Con sed o echo de Bash también se puede escribir en el archivo. Si hay archivos que quieres proteger, usa también un hook de Bash para bloquear el mismo nombre. Otra opción es escribirlo directamente en deny dentro de la configuración de permisos.

Formas de escritura que no funcionaron (probamos 8, una vez cada una)

A veces, un hook con una pequeña diferencia en la forma de escribirlo simplemente no funciona, sin mostrar ningún error. Estos son los resultados de las 8 variantes que probó AInformation. Usamos la misma petición de borrado que antes, y contamos cuántas veces se llamó al hook revisando el archivo de registro.

8 formas de escritura probadas (5 que no funcionaron y 3 que sí)

Forma de escritura Qué ocurrió Cómo corregirlo
Matcher en minúsculas: bash No se activó ni una vez y el rm se ejecutó Escribirlo como Bash, con mayúscula
exit 1 cuando se quería bloquear Se activó, pero el rm se ejecutó Usar exit 2
Se olvidaron los [ ] y se usó { } Con -p no mostró nada y pasó de largo Encerrarlo entre [ ]
La ruta se escribió en relativo Después de cd, todos los Bash se detuvieron Añadir $CLAUDE_PROJECT_DIR
No se verificó stop_hook_active El rechazo se repitió 10 veces seguidas Dejarlo pasar si es true
exit 2 y JSON juntos Se detuvo por el motivo del JSON (funcionó) Usar solo uno de los dos
exit 0 con deny en JSON Se detuvo (funcionó) Así está bien
Usar if para limitarlo solo a rm El hook se activó solo una vez (funcionó) Se pueden reducir las llamadas
Datos: investigación propia de AInformation (medición realizada el 3 de noviembre de 2026 ejecutando Claude Code 2.1.317 (Opus 5.5) en una carpeta de prueba en Mac). Cada forma se probó una vez; el número de llamadas al hook se contó a partir del archivo de registro / Elaborado por AInformation

Lo más difícil de notar es la diferencia de mayúsculas en el matcher. El hook escrito con «bash» en minúscula nunca se llamó, y el rm se ejecutó sin problema. Según la documentación oficial, el matcher debe coincidir exactamente con el nombre de la herramienta, y «Bash» «matches only the Bash tool».

La variante con exit 1, pensada para bloquear, sí activó el hook y mostró el motivo. Pero el rm se ejecutó de todas formas. Solo el código 2 bloquea la acción; los demás códigos solo notifican.

La variante donde se olvidaron los [ ] y se usó { } no mostró nada con -p y pasó de largo. Al abrir la misma carpeta en la pantalla de conversación apareció un «Settings Error», que indicaba: «Files with errors are skipped entirely». Es decir, si un archivo tiene un error, ni siquiera se leen las demás líneas correctas que contiene.

Pantalla de la terminal. Aparece el encabezado Settings Error con la ubicación de settings.json, el mensaje 'Expected array, but received object' y el texto 'Files with errors are skipped entirely' y el número 3
Al abrir la configuración donde se olvidaron los [ ], apareció un Settings Error

El error de la ruta relativa resultó ser todavía más problemático. Cuando Claude cambió de ubicación con cd memo, dejó de encontrar el archivo del hook, y la ejecución se detuvo hasta el siguiente ls. Como Python termina con el código 2 cuando no encuentra un archivo, Claude Code lo interpreta como una señal para detenerse. Por eso siempre hay que añadir $CLAUDE_PROJECT_DIR en el command.

También hubo variantes que funcionaron. La que terminaba con el código 2 y además mostraba JSON se detuvo por el motivo indicado en el JSON. La documentación oficial dice: «If your hook also printed JSON that makes a blocking decision, Claude Code uses that decision’s reason instead». La variante que usaba if con «Bash(rm *)» solo activó el hook cuando se ejecutaba rm. En la forma normal, el hook se llamó 3 veces por una sola petición, así que si el hook es pesado, limitarlo con if lo hace más rápido.

Tabla de referencia rápida de cuándo un hook no funciona. Forma escrita. Cómo corregirlo. El matcher en minúscula. Usar Bash con mayúscula. No se activó ni una vez. exit 1 cuando se quería bloquear. Usar exit 2. El rm se ejecutó igual. Se olvidaron los [ ] del arreglo. Encerrarlo entre [ ]. No se lee el archivo completo. La ruta se escribió en relativo. Usar una ubicación absoluta. Después de cd todo se detuvo. Los errores aparecen al abrir la pantalla de conversación. Ilustración elaborada por AInformation (diagrama de referencia rápida situación → solución)
Tabla de referencia rápida para cuando un hook no funciona

Creamos un mod: mostrar una barra encima del prompt y añadir un comando

Claude Code tiene, además de los hooks, un mecanismo llamado Mod. La documentación oficial lo describe así: «A hooks module: hooks written as JavaScript functions, which can also draw panes and add commands. A plugin that has one is called a mod». El hook de settings.json solo ejecuta comandos. Un Mod puede mostrar barras y paneles en pantalla, y también agregar comandos de barra diagonal. Al inicio de la descripción del tipo aparece «EARLY ACCESS», lo que significa que la forma de escribirlo puede cambiar con nuevas versiones.

Esta vez creamos un Mod que muestra en una barra el número de veces que se usaron herramientas y el número de veces que se bloqueó rm. La estructura de carpetas es la siguiente.

renshu-meter/
├── .claude-plugin/plugin.json   (nombre y descripción)
├── hooks/hooks.json             (escribe un solo archivo a cargar)
├── hooks/register.tsx           (cuerpo principal, unas 40 líneas)
└── types/index.d.ts             (forma de los valores que hay que recordar)
{ "name": "renshu-meter", "version": "0.1.0", "description": "Para práctica: muestra en una barra el número de llamadas a herramientas y el número de veces que se bloqueó rm", "types": "./types/index.d.ts" }
{ "modules": ["./register.tsx"] }

El cuerpo principal se escribe en TypeScript. Con on() se define el «cuándo». Agrupamos en un solo archivo tres tareas: dibujar la barra, responder al comando y bloquear rm.

import { atom, read, update } from 'claude-code'
import type { Register } from 'claude-code'

import type { Meter } from '../types'

// Mod de práctica: muestra en la barra superior del prompt el número de veces que se llamó a una herramienta y el número de veces que se bloqueó rm
const meter = atom({ plugin: 'renshu-meter', key: 'meter' } as const, { tools: 0, denied: 0, last: '' }, { shape: 'meter-1' })
const RM = /(^|[;&|]\s*|\s)rm\s/

export const register: Register = on => {
  on('session.start', async ($, e, next) => {
    await $.command.register({ name: 'renshu-meter', description: 'Muestra el número de usos de herramientas y el número de bloqueos' })
    return next(e)
  })

  on('command.run', { command: 'renshu-meter' }, async $ => {
    const m = await read($, meter)
    return { text: `Herramientas ${m.tools} veces, rm bloqueado ${m.denied} veces (última: ${m.last || 'ninguna aún'})` }
  })

  on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
    if (RM.test(' ' + e.command)) {
      await update($, meter, m => ({ ...m, denied: m.denied + 1, last: 'rm bloqueado' }))
      $.ui.toast('Se bloqueó rm')
      return { deny: 'No se puede usar rm. Para eliminar un archivo, muévelo con mv a trash/.' }
    }
    return next(e)
  })

  on('tool.call', async ($, e, next) => {
    await update($, meter, m => ({ ...m, tools: m.tools + 1, last: String(e.tool) }))
    return next(e)
  })

  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
    if (e.props.hasSurvey) return next(e)
    const m = await read($, meter)
    const { Box, Text } = $.ui.resolve(e)
    return (
      <Box>
        <Text bold>Barra de práctica</Text>
        <Text>Herramientas {m.tools} veces, rm bloqueado {m.denied} veces</Text>
      </Box>
    )
  })
}

Una vez escrito, lo verificamos con claude plugin validate. El resultado fue «Validation passed with warnings», con solo avisos de que faltaban autor, README, LICENSE e icono. También muestra en una lista qué hooks se usan, de modo que se pueden detectar errores de escritura de antemano.

claude –plugin-dir ./renshu-meter

Esto lo carga solo por esa vez. Si quieres compartirlo con tu equipo, agrégalo al catálogo de plugins.

Al activarlo, apareció sobre el campo de entrada el texto «Barra de práctica: herramientas 0 veces, rm bloqueado 0 veces». Cuando pedimos «borra memo/kari.txt», el Mod bloqueó rm. El número en la barra también se actualizó en ese momento.

Pantalla de la terminal. Debajo de la respuesta que bloqueó rm y lo movió a trash con mv, sobre el campo de entrada aparece la línea Barra de práctica Herramientas 2 veces, rm bloqueado 1 vez
La barra del Mod aparece sobre el campo de entrada y también bloqueó rm

Al escribir /renshu-meter, responde el comando que agregó el Mod. Si mientras está activo se modifica el texto del archivo principal, unos 4 segundos después aparece “reloaded” y la barra cambia. El número contado se mantiene sin desaparecer.

Pantalla de la terminal. La respuesta de /renshu-meter muestra Herramientas 2 veces, rm bloqueado 1 vez, y debajo aparece la línea renshu-meter: reloaded y Barra de práctica v2
La respuesta del comando del Mod y la recarga justo después de la modificación

Al probarlo también con -p, la barra no aparece, pero el Mod bloqueó rm igual. Lo movió a trash en 8.7 segundos. Si solo se necesita bloquear o agregar algo, basta con el hook de settings.json. Parece razonable usar un Mod solo cuando se quiere mostrar algo en pantalla.

Diferencias entre hooks y Mod. Se escribe en la configuración: hooks. Se agrega como componente: Mod. Ventajas: se puede escribir en cualquier lenguaje y funciona sin pantalla / se pueden agregar barras y comandos en pantalla. Desventajas: no se puede agregar nada en pantalla / se escribe en TypeScript, en fase de prueba. Si solo se necesita bloquear o agregar algo, basta con hooks. Ilustración creada por AInformation (cuadro comparativo de ventajas y desventajas de los dos tipos)
Diferencias entre hooks y Mod

Los hooks que usa AInformation (12 usos)

En la configuración personal de AInformation hay 12 hooks organizados en 6 tipos. Como el contenido está relacionado con tareas internas, solo compartimos el número y el uso de cada uno.

Hooks en la configuración personal de AInformation (12 en total, sin mostrar el contenido)

Tipo Cantidad Para qué sirve
Stop 4 Rechazar cuando falta el registro o las horas de trabajo, y abrir el documento creado
UserPromptSubmit 3 Poner un título, pasar las reglas relevantes y revisar la duración de la conversación
PreCompact 2 Escribir el traspaso antes de resumir
PreToolUse / PostToolUse 2 Control antes y después de hacer una pregunta
SessionStart 1 Evitar que la Mac entre en reposo
Datos: investigación de AInformation (el 3 de noviembre de 2026 se contaron los hooks de ~/.claude/settings.json personal por tipo) / Elaborado por AInformation

El tipo con más cantidad es Stop, con 4. Si falta el registro de observaciones o las horas de trabajo, rechaza la respuesta antes de que termine. Al igual que el rechazo de report.md que probamos antes, si solo se trata de comprobar si algo «se escribió o no», se puede crear con pocas líneas.

Al poner esto en marcha, decidimos tres cosas. El command siempre se escribe con una ruta absoluta. El hook debe terminar rápido, y los procesos pesados se ejecutan en segundo plano. Antes de usar un hook nuevo, lo probamos primero en la pantalla de conversación de una carpeta de prueba, para confirmar que no aparece la pantalla de error de configuración.

Si algo no funciona bien, primero revisa con /hooks si se está cargando. Si quieres aislar el problema deteniendo todos los hooks, al iniciar con claude –safe-mode se arranca sin leer los hooks. La forma en que aparecen los errores está resumida en el artículo sobre errores y límites de Claude Code, y los comandos que se escriben con / están en la lista de comandos de Claude Code. La comparación con otras herramientas de programación con IA está en el centro de programación con IA.

Si antes de escribir hooks quieres saber cómo pedirle cosas a Claude Code y cómo manejar la pantalla de permisos, revisa la introducción a Claude Code para no ingenieros.

Las correcciones y configuraciones nuevas de hooks se incorporan en cada versión. Los cambios de esta semana se traducen al japonés cada semana en los cambios semanales de Claude Code.

Resumen

Los hooks de Claude Code funcionan si escribes el momento, la herramienta y el comando en settings.json. AInformation escribió 5 tipos y los ejecutó 3 veces cada uno, y en todos los casos, en las ejecuciones con hook, ocurrió siempre el comportamiento definido. Los cinco son: pasar la nota anterior, bloquear claves, bloquear rm, ejecutar pruebas y hacer que se escriba un informe. El matcher se escribe con las mismas mayúsculas que el nombre de la herramienta. Para bloquear, se termina con 2 y la ruta se escribe desde $CLAUDE_PROJECT_DIR. El hook Stop revisa stop_hook_active y escribe de forma concreta qué hacer en el motivo. Si quieres agregar una barra en pantalla o comandos, también puedes usar Mod.

Preguntas frecuentes

¿Qué son los hooks de Claude Code?
Es un mecanismo que ejecuta tu propio comando en un momento determinado de Claude Code. A diferencia de las reglas de CLAUDE.md, se ejecuta siempre, sin importar si la IA las lee o no.
¿Dónde se escriben los hooks?
Se escriben en «hooks» dentro de settings.json. Si quieres que apliquen a todas las carpetas, usa ~/.claude/settings.json; si solo quieres que apliquen a una carpeta, usa .claude/settings.json. Para pruebas solo tuyas, escríbelos en .claude/settings.local.json.
¿Cómo se bloquea la llamada a una herramienta con un hook?
Con un hook PreToolUse, termina con 2 y muestra el motivo en la salida de error estándar. También se logró bloquear terminando con 0 y escribiendo deny en permissionDecision dentro del JSON. Si termina con 1, no se bloquea.
El hook no funciona, ¿qué puede estar pasando?
Revisa si el matcher está escrito en mayúsculas como Bash, y si está encerrado entre [ ]. Si lo ejecutas en la pantalla de conversación, aparece un error de configuración. También confirma si aparece en la lista con /hooks.
Si corrijo settings.json, ¿necesito reiniciar?
Cuando lo probó AInformation, el hook agregado funcionó de inmediato sin reiniciar. La documentación oficial también indica que, al vigilar el archivo, normalmente se vuelve a leer.
¿Cómo se hace que el hook Stop haga que el trabajo continúe?
En el JSON, escribe «block» en «decision» y lo que se debe hacer en «reason». Si stop_hook_active es true, hay que dejarlo pasar, o de lo contrario se rechazará una y otra vez. Se corta después de 8 veces seguidas.
¿Escribir un hook hace que todo sea más lento?
Los hooks de prueba de esta vez tardaron unos 20 milisegundos cada uno. En el caso de hooks pesados, como los que ejecutan pruebas, si limitas los casos en que se activan con un if, se reduce la cantidad de veces que se llaman.
¿Los hooks funcionan también con -p?
Sí, funcionan. Las 30 mediciones reales de esta vez se hicieron todas con -p. Sin embargo, los errores de configuración no se notifican con -p, así que conviene abrir el hook nuevo una vez en la pantalla de conversación para confirmarlo.
¿Qué es Mod en Claude Code?
Es un plugin que se escribe en TypeScript. Además de hooks, permite agregar barras en pantalla, paneles y comandos de barra diagonal. Se puede cargar y probar con –plugin-dir. Por ahora está en una etapa de publicación temprana, y la forma de escribirlo puede cambiar.
¿Cómo se diferencian los hooks y los skills?
Usa hooks para lo que quieras bloquear o agregar sin excepción, y skills cuando quieras que se recuerde un procedimiento de trabajo. Como los skills los lee la IA para usarlos, puede que no siempre se usen.

Siguiente artículo recomendado

SHARE

Incluimos publicidad y podemos recibir una comisión por las solicitudes realizadas desde ella.

También te puede interesarRELATED