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.
- 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 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 |
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 |
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 |
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.

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 |
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.

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”.

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).

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ó”.

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.

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».

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 |
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.

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)](https://ainformation.jp/wp-content/uploads/2026/11/hk_il_lookup.jpg)
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.

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.

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.

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 |
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.

