← Volver al Blog

/compact Claude Code: 3 hooks pre-compact

El comando /compact de Claude Code comprime la conversación entera en un único resumen cuando la ventana de contexto se llena. Lo que se pierde ahí no suele ser el código (eso ya está en los archivos del repo) sino los hechos operativos que vivían solo en la conversación: “este push ya está aprobado”, “esta aproximación la probamos y falló”, “el valor viene de aquel archivo”. Después del /compact, el agente vuelve a pedir aprobaciones y repite intentos ya descartados.

Hay un plugin oficial-de-la-comunidad para esto, compact-ops, derivado de u-ichi/compact-plus (MIT). No modifica el algoritmo de compactación: engancha 3 hooks oficiales de Claude Code alrededor del /compact para que la parte operacional sobreviva. Esta nota recorre esos 3 hooks, el diseño fail-open que los sostiene, y las trampas que aparecen cuando uno los activa en una sesión real.

3 hooks del plugin compact-ops que guardan el estado antes del /compact de Claude Code: UserPromptSubmit al 60%, PreCompact escribe state file, SessionStart reinyecta

Qué borra el /compact (y qué no)

Antes de los hooks conviene entender qué se pierde.

Claude Code, cuando la conversación llega al límite de contexto (200k tokens en los modelos actuales), toma todo el historial y le pide al mismo modelo que lo resuma. Pasa con el /compact manual y pasa con el auto-compact. El resumen cumple en promedio, pero deja fuera cosas específicas:

  • Fragmentos de código compartidos en el chat se aplastan a “el usuario compartió código de X”. La firma exacta de la función, se perdió.
  • Decisiones con matiz (“vamos con B pero solo si Y no cambia”) se vuelven “decidimos B”.
  • Restricciones dichas una sola vez (“nunca uses --force”) tienen alta chance de desaparecer del resumen.
  • Estado del filesystem (qué archivos se abrieron, qué se probó, qué falló) se vuelve un párrafo abstracto.

Lo que sí sobrevive bien son los nombres de archivo, los conceptos generales y la última intención del usuario. Lo que peor sobrevive son los hechos de operación: aprobaciones, intentos fallidos, convenciones acordadas durante la sesión.

Los 3 hooks del plugin están diseñados exactamente para ese hueco.

Hook 1 — UserPromptSubmit: aviso al 60% de uso

El primer hook no interviene en el /compact: trata de que no te agarre de sorpresa.

UserPromptSubmit corre antes de que el modelo procese cada prompt del usuario. El hook lee el usage del último mensaje del asistente desde el transcript, calcula el porcentaje de contexto consumido, y si pasa el umbral (default 60%, cambiable con COMPACT_OPS_WARN_THRESHOLD) inyecta un aviso una sola vez por sesión:

La ventana de contexto va al 62%. Buen momento para /compact. Plan activo: X. Fase: Y. Últimas 3 decisiones: …

Las 3 líneas de “recitation” (Plan / Fase / Decisiones recientes) no son estéticas: son una lectura concreta del estado actual, no una invitación abstracta. Si el agente va a comprimir en los próximos turnos, al menos esas 3 líneas quedan al final del transcript y tienen chance de sobrevivir en el resumen.

¿Por qué 60% y no 80%? Porque entre el 60% y el momento en que el auto-compact dispara hay varios turnos todavía usables. Si avisara al 85%, el usuario estaría casi forzado a interrumpir lo que esté haciendo. A 60% hay margen para terminar el párrafo, cerrar el checkpoint y entonces pedir /compact deliberadamente. El valor es ajustable; el default parte de ahí.

Un detalle del diseño: el aviso se emite una sola vez. Si cada turno te recordara que vas al 61%, el hook se vuelve ruido y lo terminás silenciando. El cooldown se resetea después del PostCompact.

Hook 2 — PreCompact: state file de 10 secciones

Este es el hook central. Corre inmediatamente antes de que /compact genere el resumen built-in. Hace dos cosas en orden:

1. Backup del transcript completo a ~/.claude/compact-ops/backups/<cwd_hash>/<session_id>.jsonl.gz. El .jsonl original de Claude Code pesa decenas de MB en sesiones largas; con gzip baja a ~1/10. 30 días de retención.

2. Llama a un LLM auxiliar (default Sonnet, fallback Haiku, configurable) que recibe el transcript condensado y escribe un state file de 10 secciones fijas en ~/.claude/compact-ops/state/<cwd_hash>/<session_id>.md:

# Compact Prep State
## Active Plan
## Current Phase
## TaskList Summary
## Session Decisions
## Constraints and Blockers
## Worker Topology
## Skills Invoked
## Editing Files
## Failed Attempts
## Recovery Notes

La elección de secciones no es decorativa. Session Decisions, Failed Attempts y Constraints and Blockers son exactamente los 3 ejes que el /compact built-in diluye más. Darles un heading propio obliga al LLM auxiliar a producir contenido ahí, en lugar de resumir todo en un párrafo que mezcla “qué pasó” con “qué se decidió”.

El state file no es un reemplazo del resumen built-in. El /compact corre igual y produce su summary; el state file queda al lado, en disco, como segunda fuente. El agente post-compact tiene entonces dos hilos: el summary (narrativa) y el state file (hechos operativos estructurados).

Un patrón importante del diseño: el plugin inyecta explícitamente la nota “relee los archivos originales antes de confiar en este estado”. Un LLM escribiendo un resumen para que otro LLM lo lea, sin verificación cruzada contra la fuente, es un juego del teléfono acelerado. La guía de recuperación lo dice en texto plano para que el agente post-compact no trate el state file como verdad canónica.

Hook 3 — SessionStart: reinyectar al retomar

El tercer hook cubre dos casos:

Caso compact. Terminada la compactación, Claude Code arranca el próximo turno con la ventana nueva. SessionStart con matcher compact dispara ahí y inyecta en esa ventana nueva: el state file completo, la nota “relee antes de confiar”, y la lista de skills que ya se invocaron en la sesión previa. El agente ve esto antes de que el usuario hable.

Caso resume. Cuando cerrás el laptop, volvés al día siguiente y haces claude --resume, SessionStart con matcher resume corre. Busca el state file de la sesión que estás reanudando. Si no existe (porque reiniciaste la máquina y la sesión activa cambió), cae a buscar el state más reciente del mismo proyecto en las últimas 72 horas (COMPACT_OPS_RESUME_MAX_AGE_HOURS). Esto es la pieza que no tenía el proyecto original: compact-plus guarda el state en $TMPDIR, que se borra al reiniciar.

El fallback por proyecto es intencionalmente laxo. Si trabajaste anoche en el repo X y hoy abrís una sesión nueva en el mismo repo, el state de anoche se re-inyecta aunque la sesión no sea literalmente la misma. En proyectos donde volvés al mismo contexto durante días, es exactamente lo que querés.

Fail-open: el día que los 3 hooks fallaron

Diseñar un plugin que corra en cada /compact significa que un bug en el plugin puede romper /compact para todo el mundo. La respuesta de este diseño es fail-open: si el hook falla, el flujo oficial de Claude Code sigue como si el hook no existiera. No lo bloquea, no corta el turno, no cancela el /compact.

Esto no es una idea abstracta. En el log de dogfooding de la v0.2.0 (JA) del propio plugin, durante la sesión de release, se cambió la estructura de directorios del plugin y la sesión activa tenía rutas obsoletas en memoria. Al llegar el /compact, los 3 hooks fallaron en secuencia (paths rotos). El /compact nativo corrió limpio igual. El transcript se comprimió, la sesión siguió, y los errores quedaron solo en la línea de debug.

El fail-open se paga con silencio. Si no activás COMPACT_OPS_DEBUG=1, no vas a notar que un hook está muriendo cada compact. El transcript no cambia, el /compact nativo responde igual. Esa es la razón por la que el env var de debug existe antes que cualquier otra configuración: fail-open sin logs es fail-open invisible, y después de 3 compacts fallidos la gente desinstala el plugin pensando que no hace nada. Si vas a adoptarlo, prendé el debug la primera semana.

Lo que el plugin no es

Vale aclarar los límites antes de instalarlo:

  • No reemplaza el summary del /compact. Lo complementa. El algoritmo de compactación de Claude Code queda intacto.
  • No es “memoria para siempre”. El state file dura 30 días por default. Para memoria estable a nivel proyecto conviene un MEMORY.md o CLAUDE.md versionado en el repo; hay un recorrido de 3 patrones de memoria complementarios en esta otra nota.
  • No evita que el agente post-compact se equivoque. Da más contexto, pero el agente tiene que leer archivos originales igual. La guía de recuperación lo dice explícitamente.
  • No es gratis en LLM calls. Cada /compact suma 1 llamada al modelo auxiliar (Sonnet o Haiku). En sesiones largas se nota en el consumo si estás en free tier. COMPACT_OPS_PRIMARY_BACKEND="" lo desactiva y deja solo el backup del transcript.

Instalación mínima

git clone https://github.com/kenimo49/compact-ops.git
claude plugin marketplace add /path/to/compact-ops --scope user
claude plugin install compact-ops@compact-ops-local

Requisitos: Claude Code v2.x, jq, y el CLI claude disponible como backend LLM. Linux y macOS (el plugin tolera md5sum / md5 / shasum en ese orden, no requiere GNU exclusivos).

Después de instalar, lo único que hay que recordar es activar debug la primera vez:

# en ~/.claude/settings.json dentro de "env"
"COMPACT_OPS_DEBUG": "1"

Y usar /compact como siempre. El resto son 3 hooks oficiales trabajando en silencio.


ken imoto · kenimoto.dev

Convirtiendo LLMs de Mentirosos en Expertos Libro relacionado Convirtiendo LLMs de Mentirosos en Expertos Ingeniería de Contexto desde cero — RAG, MCP, CLAUDE.md y Agentic RAG, con benchmarks que muestran hasta 4,6× de mejora Ver la página del libro →