TL;DR y veredicto
Cuatro tareas que no paras de escribir en Claude Code pertenecen cada una a un archivo distinto. Una checklist de release es un skill. Una regla que nunca debe romperse es un hook PreToolUse. Una auditoría que lee muchos archivos es un subagent. Los datos que Claude no puede ver son un servidor MCP.
Ejecutamos las cuatro en un repo real, una API de facturación con 34 tests, y cada vez hicimos primero la elección equivocada. Las elecciones equivocadas fallaron en pantalla:
- La checklist en CLAUDE.md añadió una línea
Memory files: 210 tokensa /context, en cada turno. - El primer hook se ejecutó, imprimió su aviso y la escritura pasó (unitPrice de 8000 a 12000).
- La auditoría en línea llenó el hilo: Messages en 19.3k tokens, frente a 7.2k con un subagent nombrado.
- Sin servidor de docs, Claude respondió "I couldn't find that setting... I won't guess one."
Veredicto: elige el tipo con la tabla de la doc, y luego haz que el archivo responda, no solo que exista. Un hook que solo registra es consultivo, como la línea de CLAUDE.md que sustituyó. De los cuatro, solo el hook nunca pregunta.
Copia los archivos de abajo, haz commit y empieza por el que más reescribes.
Síntesis con fuentes
Lo que carga tu sesión antes de tu primera palabra. Nuestro /context de referencia marcaba 26.2k/1m tokens, sin línea de memoria ni línea de MCP. Cada tarea que añades pone una línea en esa tabla o no. Léela primero y mide contra ella.
Una regla de CLAUDE.md es una petición, un hook es una aplicación estricta. La doc dice que una instrucción como "never edit .env" en CLAUDE.md o en un skill es una petición, y que un hook PreToolUse que bloquea la edición es una aplicación estricta s1. La página Memory describe CLAUDE.md como contexto, no como aplicación estricta s8. Un hilo de Hacker News contó lo mismo desde el lado del hook: un hook que termina con 0 no bloquea nada s10.
La tabla de disparadores te dice qué archivo. La tercera vez que pegas un procedimiento: un skill. Una salida que inunda el chat: un subagent. Una pestaña que Claude no ve: un servidor MCP. Algo que debe ocurrir siempre: un hook s1.
Tarea 1, la checklist de release, es un skill.
- Un procedimiento que creció dentro de CLAUDE.md pertenece a un skill, cuyo cuerpo se carga solo al usarlo s3.
- En nuestra prueba, la checklist pegada en CLAUDE.md añadió una línea
Memory files: 210 tokens. Como skill condisable-model-invocation: true, /context volvió a la referencia. /releaseejecutó seis pasos (34 tests, 0.4.0 a 0.4.1, tag v0.4.1). También detectó quegit commit -amse salta los archivos sin seguimiento, así que añadió primero el nuevo changelog al área de preparación.
Tarea 2, la regla que debe cumplirse, es un hook, y la garantía es la respuesta que devuelve.
- Exit 0 sin salida no es una decisión. Exit 2 bloquea. El JSON deny también bloquea s2.
- Nuestro primer hook registraba, terminaba con 0 y la escritura pasó (unitPrice de 8000 a 12000). La respuesta de Claude nunca mencionó el hook.
- El mismo hook devolviendo el JSON deny rechazó la edición con
PreToolUse:Edit hook error: Stored data is never edited by hand: write a migration in src/store/migrations.ts (README § Conventions).Claude entonces preguntó: saltarse la protección o escribir la migración. - La doc publica una receta PreToolUse para archivos protegidos s23.
- El límite: este hook vigila Edit y Write. Cubre también el shell, o mantén el shell fuera de tu lista de permitidos.
onFailure: "block"hace que un hook fallido bloquee en lugar de dejar pasar. Llegó en la v2.1.295 s2 s15.
Tarea 3, la auditoría que inunda, es un subagent.
- Un subagent empieza a ciegas y devuelve un resumen s4.
- En línea, Messages marcaba 19.3k. Con el subagent nombrado, 7.2k.
- El archivo de agent por sí solo delegó 0 de 2 veces. Nombrarlo en el prompt hizo que delegara. El archivo lo hace existir, tu prompt hace que Claude lo use.
- Fija
model:en el archivo. La factura no se movió: la lectura sigue ocurriendo, solo que no en tu ventana.
Tarea 4, los datos fuera del alcance de Claude, es un servidor MCP.
claude mcp add --scope projectescribe.mcp.json, y la búsqueda de herramientas carga solo los nombres de las herramientas s5.- Existe un servidor HTTP gratuito, sin login, para las docs del propio Claude Code, con
search_claude_code_docsyquery_docs_filesystem_claude_code_docss24. - Sin él, Claude respondió "I couldn't find that setting... I won't guess one." Con él, Claude respondió
"onFailure": "block", añadido en la v2.1.295, mientras que nuestra instalación era la 2.1.294. - Un servidor de docs para una librería que el modelo ya conoce no cambió nada para nosotros. Conecta lo que el modelo no puede tener.
Informes de campo. Un hilo de Reddit contó que Claude ignora una línea "MUST use agent" en CLAUDE.md s9. Lo citamos como un testimonio, no como una medición. Las publicaciones de ingeniería de Anthropic tratan la misma separación s6 s7.
Código
Cuatro archivos de la prueba, más dos utilidades. Las rutas indican dónde vive cada uno en tu repo.
1. El skill de release, en .claude/skills/release/SKILL.md. Escribe /release. disable-model-invocation: true hace que solo tú puedas iniciarlo, que es lo que quieres para algo que hace commit y tag.
SKILL.md
---
name: release
description: Cut a patch release of ledgerly (typecheck, tests, version bump, changelog line, commit, tag).
disable-model-invocation: true
---
Run these steps in order and stop at the first failure:
1. `pnpm typecheck` must exit 0.
2. `pnpm test` must pass.
3. Bump the patch version in `package.json` (e.g. 0.4.0 -> 0.4.1).
4. Add one line at the top of `CHANGELOG.md` (create it if missing): `## vX.Y.Z - YYYY-MM-DD` followed by a one-line summary of the commits since the last tag.
5. `git commit -am "release vX.Y.Z"`
6. `git tag vX.Y.Z`. Never push.
2. El hook que protege los datos, en .claude/settings.json. Devuelve la decisión deny con un motivo que Claude lee. Necesita jq en tu PATH y cabe en línea, así que el hook no necesita archivo de script. Cambia el patrón data/.*\.json$ y el motivo según tu regla.
settings.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -c 'if (.tool_input.file_path // \"\") | test(\"data/.*\\\\.json$\") then {hookSpecificOutput: {hookEventName: \"PreToolUse\", permissionDecision: \"deny\", permissionDecisionReason: \"Stored data is never edited by hand: write a migration in src/store/migrations.ts (README § Conventions).\"}} else empty end'"
}
]
}
]
}
}
3. El auditor de convenciones, en .claude/agents/conventions-auditor.md. Las herramientas se limitan a las de solo lectura y el modelo está fijado. Edita la lista de convenciones para que coincida con tu README.
conventions-auditor.md
---
name: conventions-auditor
description: Use when asked to audit the code against the README conventions
model: sonnet
tools: Read, Grep, Glob
---
You audit ledgerly's source against README § Conventions:
- Money is always an integer number of cents. Never floats.
- Domain functions return new objects; they never mutate their input.
- Validation failures throw `ValidationError` (400), missing records `NotFoundError` (404), illegal state transitions `ConflictError` (409).
- A change of stored shape bumps `schemaVersion` and adds a migration in `src/store/migrations.ts`.
Read every file under `src/`. Return only the findings: one line per violation as `file:line - convention - what is wrong`, then borderline cases the same way, then one line with the count of files read. No file contents, no summary of clean files.
conventions-auditor.md on GitHub
4. La configuración MCP, guardada como .mcp.json en la raíz del repo. Es lo que escribe claude mcp add --transport http claude-docs --scope project https://code.claude.com/docs/mcp. Claude Code lista un servidor de proyecto como pendiente hasta que lo apruebes en tu próxima sesión.
mcp.json
{
"mcpServers": {
"claude-docs": {
"type": "http",
"url": "https://code.claude.com/docs/mcp"
}
}
}
Utilidades. ctx.sh ejecuta /context en una sesión sin interfaz e imprime las líneas de tokens, para comparar antes y después. mcp-probe.sh envía initialize y tools/list a una URL MCP HTTP, para comprobar que un servidor responde antes de añadirlo.
ctx.sh
#!/usr/bin/env bash
# Usage: ctx.sh <step> -> runs "/context" through run-session.sh, writes context-<step>.txt (honours STRICT_MCP / EXTRA_ALLOW)
C="$(cd "$(dirname "$0")" && pwd)"
"$C/run-session.sh" "$C/out/context-$1.json" "/context" >/dev/null
python3 -c "import json,sys;d=json.load(open(sys.argv[1]));print(d['result'])" "$C/out/context-$1.json" > "$C/context-$1.txt"
grep -E '^\*\*Tokens|^\| (System prompt|System tools|MCP tools|Memory files|Skills|Custom agents|Messages) ' "$C/context-$1.txt"
mcp-probe.sh
#!/usr/bin/env bash
# Usage: mcp-probe.sh <url> [tools/call json params] -> initialize + notifications/initialized + tools/list (+ optional tools/call) over streamable HTTP
U="$1"; H=$(mktemp)
curl -s -D "$H" -m 30 -X POST "$U" -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}'
echo; SID=$(grep -i '^mcp-session-id:' "$H" | tr -d '\r' | cut -d' ' -f2); echo "session-id: ${SID:-none}"
HS=(-H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream'); [ -n "$SID" ] && HS+=(-H "Mcp-Session-Id: $SID")
curl -s -m 30 -X POST "$U" "${HS[@]}" -d '{"jsonrpc":"2.0","method":"notifications/initialized"}' >/dev/null
curl -s -m 30 -X POST "$U" "${HS[@]}" -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'; echo
[ -n "${2:-}" ] && { curl -s -m 90 -X POST "$U" "${HS[@]}" -d "{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",\"params\":$2}"; echo; }
rm -f "$H"
Hazlo el lunes
- Abre una sesión nueva y ejecuta /context. Apunta el total. Esa es tu referencia.
- Elige la checklist que más pegas. Muévela a
.claude/skills/<name>/SKILL.md, añadedisable-model-invocation: truesi hace commit o tag, y ejecútala con/<name>. - Toma la regla que nunca debe romperse y ponla en un hook PreToolUse que devuelva
permissionDecision: "deny"o termine con 2. Pídele a Claude que rompa la regla y comprueba que la edición se rechaza. - Si tu hook solo registra, cámbialo. Un hook que termina con 0 no bloquea nada.
- Mueve una tarea que lee muchos archivos a
.claude/agents/<name>.mdcon unmodel:fijado. Nómbrala en tu prompt hasta que Claude la elija solo. - Comprueba un dato que Claude adivinó para ti hace poco. Si es más nuevo que el modelo, añade un servidor de docs con
claude mcp add --transport http <name> --scope project <url>y haz commit de.mcp.json. - Comprueba tu versión de Claude Code antes de apoyarte en un ajuste.
onFailure: "block"necesita la 2.1.295. - Ejecuta
git add -A && git statusy haz commit de los archivos nuevos. Nuestra prueba mostró exactamente cuatro.
Ir más allá
La tarjeta de decisión. Una página. Son las filas de la doc s1 con nuestras cuatro tareas escritas encima. El corazón de este pack son los cuatro archivos de arriba más esta tarjeta.
| Tipo | Qué hace | Úsalo cuando | Nuestra tarea | Nuestro archivo |
|---|---|---|---|---|
| CLAUDE.md | Contexto persistente cargado en cada conversación | Una convención que Claude falla dos veces | Una línea, no un procedimiento | ninguno |
| Skill | Instrucciones y flujos de trabajo que Claude puede usar | Pegas el mismo playbook por tercera vez | La checklist de release | .claude/skills/release/SKILL.md |
| Hook | Un script disparado por eventos | Quieres que ocurra siempre, sin preguntar | No editar nunca a mano los datos almacenados | .claude/settings.json |
| Subagent | Contexto aislado que devuelve resultados resumidos | Una tarea secundaria inunda tu conversación | La auditoría de convenciones | .claude/agents/conventions-auditor.md |
| MCP | Conecta con servicios externos | Copias datos una y otra vez desde una pestaña que Claude no ve | Las docs de Claude Code | .mcp.json |
| Plugin | Agrupa los cuatro en una unidad instalable | Un segundo repositorio necesita la misma configuración | No usado | ninguno |
La tarjeta decide el tipo, no la calidad. Un hook que solo registra, un subagent que nunca nombras y un servidor para docs que el modelo ya conoce estaban cada uno en el tipo correcto y aun así fallaron. Elige el tipo, y luego haz que el archivo responda.
Cuándo no recurrir a esto.
- Una convención que Claude sigue haciendo mal se queda en CLAUDE.md como una línea, no como un procedimiento.
- Una tarea que diriges de un lado a otro, con tu historial a la vista, se queda en el hilo principal, no en un subagent.
- Un servidor de docs para una librería que el modelo ya conoce cuesta contexto y no cambia nada.
pnpm testes un CLI que ya tienes. No es una tarea para MCP.
Plugins. Un plugin es la caja que envía los cuatro juntos. Recurre a uno cuando otro repo necesite la misma configuración.
Skill más MCP. La doc los empareja: el servidor aporta la conexión, y el skill enseña a Claude a usarla bien.
Skill con context: fork. Un skill puede ejecutarse en un subagent bifurcado, para que su lectura quede fuera de tu ventana. Pruébalo en una tarea antes de fiarte.
Tipos de hook más allá de command. Command es el que usamos. La referencia de hooks lista otros tipos de manejador, incluidos los hooks http y prompt. Vale la misma regla: lo que devuelve el hook decide, y un hook silencioso no decide nada.
MCP frente a CLI. Hay un debate abierto sobre los servidores MCP que se comen el contexto, con una publicación que defiende el CLI primero s11 y otra sobre el coste de contexto de los servidores MCP s12. Nuestra regla: si un CLI ya hace el trabajo, úsalo. Usa MCP para lo que no tiene ninguno. La búsqueda de herramientas ahora carga solo los nombres, así que mide con /context en tu propia configuración antes de decidir s5.
Mantén los skills cortos y verificados. Un skill corto por tarea, comprobado con una prueba real. Mira el artículo de SkillsBench s13 y una visión de profesional sobre por qué los skills cuestan poco hasta que se usan s14.
FAQ
¿Por qué mi hook se ejecutó pero la edición pasó igualmente? Un hook solo decide por lo que devuelve su comando. Exit 0 sin salida no es una decisión s2. Devuelve el JSON deny, o termina con 2.
¿Por qué Claude lee los archivos él mismo cuando tengo un subagent? En nuestra prueba, el archivo de agent por sí solo delegó 0 de 2 veces. Nómbralo en el prompt, por ejemplo "use the conventions auditor subagent".
¿Cubre el hook todo lo que Claude puede hacer? No. El nuestro apunta a Edit y Write. En una prueba posterior Claude se ofreció a hacer el cambio por el shell, así que cubre el shell o mantenlo fuera de tu lista de permitidos.
¿Debo añadir un servidor MCP por cada librería que uso? No. Si el modelo ya conoce la librería, un servidor no cambia nada y cuesta contexto. Añade uno para datos más nuevos que el modelo o fuera de tu disco, y compara tu versión instalada con la que añadió la función.
¿Puedo hacer commit de todo esto?
Sí. El skill, el archivo de ajustes, el archivo de agent y .mcp.json viven en el repo, así que cualquiera que lo clone los recibe. Aprueba el servidor MCP del proyecto una vez en tu próxima sesión.
AIDive