Desde la 2.1.299 (14 oct), Claude Code compacta solo, en la terminal, una conversación de más de 200,000 tokens que lleva unos 54 minutos quieta. Y desde la 2.1.295 (8 oct), un hook puede frenar la acción si falla.
Dejo sesiones de Claude Code abiertas todo el día. Salgo a una llamada, vuelvo una hora después y sigo donde iba. Lo que no veía es lo que cuesta ese regreso. Uno de los dos cambios de estos días ataca justo eso. El otro toca algo que yo daba por hecho: que mis hooks siempre corren.
Mis números
Hace unas semanas conté mis propias sesiones desde los registros que guarda Claude Code. En 14 días de septiembre fueron 264 sesiones y 9,249 peticiones. Así se repartió el costo:
Son dos cuentas distintas. Esta mide costo y la hice el 30 de septiembre sobre los registros de esos 14 días. Las 195 sesiones de qué es Claude Code cuentan solo sesiones que abrí yo, sin subagentes, con los registros que tenía el 12 de octubre.
- Releer la caché
- 69%
- Crear la caché y respuestas
- 31%
Más de dos tercios del costo es releer lo que ya estaba en la conversación, no lo que la IA escribe. Y seis sesiones se llevaron el 57% de todo. Eran las largas: cientos de peticiones sobre la misma conversación.
Así lo conté. Claude Code guarda cada sesión como un archivo en tu Mac, y cada respuesta trae su cuenta de tokens: los que leyó de la caché, los que escribió en ella y los que produjo. Sumé esas tres cantidades por sesión y le di a cada una su peso. Son pesos parecidos a los de la factura, no la factura.
Si quieres poner números a lo tuyo antes de cambiar nada, la calculadora de costo de agentes usa precios del día.
Por qué volver cuesta tanto
Un ejemplo con números redondos. Tienes una conversación de 200,000 tokens, sales una hora y al volver escribes «sigue».
- Con la caché viva, Claude Code relee los 200,000 tokens. Pesan 0.1 cada uno: 20,000 unidades.
- Con la caché vencida, tiene que escribirlos otra vez. Pesan 1.25 cada uno: 250,000 unidades.
- Mismo mensaje, mismo resultado, y 12.5 veces más peso (250,000 entre 20,000).
No es dinero, son mis unidades. La proporción es lo que importa: el mensaje que escribes al volver de una llamada de una hora paga la conversación entera como si fuera nueva. Con la tarifa publicada de escritura de una hora (2 en vez de 1.25) serían 20 veces.
- Minuto 0Sales a una llamada. La conversación ya pesa más de 200,000 tokens.
- Minuto 54Con la 2.1.299, Claude Code la compacta en la terminal.
- Minuto 60La caché de una hora se vence. Sin compactar, el siguiente mensaje reconstruye todo.
- VuelvesCon compactación sigues desde el resumen. Sin ella, pagas la reconstrucción completa.
1. Compacta cuando te vas
Claude Code guarda tu conversación en una caché que dura una hora. Mientras está viva, cada mensaje sale barato. Si te vas más de una hora, la caché se pierde y tu próximo mensaje tiene que volver a procesar toda la conversación.
La compactación en reposo es eso: Claude Code resume una conversación larga mientras no estás, antes de que la caché se venza. Llegó en la versión 2.1.299, del 14 de octubre: si una conversación de más de 200,000 tokens se queda quieta unos 54 minutos en la terminal, se compacta sola. Cuando vuelves, sigue desde el resumen y no desde cero.
Fuentes: changelog oficial de Claude Code, 2.1.299; Classmethod, 14 oct 2026
Lo que pierdes: compactar es resumir, y un resumen deja detalles fuera. Si estás en medio de algo donde cada detalle cuenta, se apaga con "idleCompaction": false en tu settings.json. El umbral de 200,000 se cambia con CLAUDE_CODE_IDLE_COMPACT_MIN_TOKENS.
Lo dejo prendido. La comparación:
| Prendida | Apagada | |
|---|---|---|
| Si te vas más de 54 minutos con más de 200,000 tokens | Se resume sola y al volver sigues desde el resumen | No se toca: pasada la hora, el mensaje de vuelta procesa todo |
| Qué pierdes | Detalles que el resumen deja fuera | Nada de la conversación, pero pagas la reconstrucción |
| Dónde la usaría | Sesiones largas de trabajo con pausas, como llamadas o el almuerzo | Una depuración donde cada línea del error cuenta |
Lo que medí el 19 de octubre
El 16 de octubre escribí aquí que no había medido cuántas de mis seis sesiones caras pasaron de 200,000 tokens. Ahora medí todas mis sesiones guardadas, no solo esas seis.
La cuenta es sencilla. Para cada sesión ordeno las respuestas por hora. Si entre dos respuestas pasan 54 minutos o más y la conversación ya pesaba 200,000 tokens o más, cuento una vuelta. Después miro esa respuesta: si leyó de la caché menos de la mitad de lo que ya había en la conversación, la cuento como caché reconstruida.
| Lo que conté | Resultado |
|---|---|
| Sesiones con 3 o más respuestas | 698 |
| Pasaron de 200,000 tokens | 120 |
| Con al menos una pausa de 54 min o más por encima de 200,000 | 63 |
| Vueltas tras esa pausa | 470 |
| Entre 54 min y 3 h | 190 |
| Entre 3 h y 12 h | 102 |
| Más de 12 h | 178 |
| Con la caché casi toda reconstruida | 452 de 470 |
| En la terminal (cli) | 9 sesiones, 10 vueltas |
| En la app de escritorio | 54 sesiones, 460 vueltas |
Veo tres cosas. Primero, 63 de mis 120 sesiones grandes, más de la mitad, tuvieron al menos una de esas pausas. Segundo, en 452 de 470 vueltas la caché ya no estaba. Tercero, casi todo pasa en la app de escritorio: 54 de las 63 sesiones y 460 de las 470 vueltas. El changelog de la 2.1.299 dice que la compactación en reposo llegó «en la terminal», así que no sé si la app la aplica. En la terminal solo conté 10 vueltas.
Las 190 vueltas de entre 54 minutos y 3 horas, una llamada o el almuerzo, son el caso que la función describe. Lo que no sé: qué hace con las 178 de más de 12 horas, que son noches y fines de semana. Tampoco medí cuántas habrían cambiado con la 2.1.299. Mi claude de terminal está en la 2.1.294, pero la app de escritorio ya corre la 2.1.299 en 28 de mis 60 sesiones más recientes, así que esta cuenta mezcla antes y después. Y una pausa entre dos respuestas puede ser también una herramienta lenta; no lo separé.
Si quieres tu número, este es el script que usé. Solo lee y solo imprime totales:
# contar-vueltas.py: solo lee. Imprime totales, nunca el contenido de tus conversaciones.
import glob, json, os
from datetime import datetime
PAUSA = 54 * 60 # segundos sin respuesta
GRANDE = 200_000 # tokens de contexto
def contexto(u):
return sum(u.get(k) or 0 for k in ("input_tokens", "cache_read_input_tokens", "cache_creation_input_tokens"))
sesiones = grandes = con_pausa = 0
vueltas = []
sesiones_cli = 0
for ruta in glob.glob(os.path.expanduser("~/.claude/projects/*/*.jsonl")):
resp = {}
cli = False
for linea in open(ruta, errors="ignore"):
try:
d = json.loads(linea)
except ValueError:
continue
cli = cli or d.get("entrypoint") == "cli"
u = (d.get("message") or {}).get("usage")
if d.get("type") != "assistant" or d.get("isSidechain") or not u or not d.get("timestamp"):
continue
t = datetime.fromisoformat(d["timestamp"].replace("Z", "+00:00")).timestamp()
clave = (d["message"].get("id"), d.get("requestId"))
if clave not in resp or contexto(u) >= resp[clave][1]:
resp[clave] = (t, contexto(u), u.get("cache_read_input_tokens") or 0)
seq = sorted(resp.values())
if len(seq) < 3:
continue
sesiones += 1
grandes += max(s[1] for s in seq) >= GRANDE
hubo = False
for antes, despues in zip(seq, seq[1:]):
if despues[0] - antes[0] >= PAUSA and antes[1] >= GRANDE:
hubo = True
vueltas.append(((despues[0] - antes[0]) / 60, despues[2] < antes[1] * 0.5, cli))
con_pausa += hubo
sesiones_cli += hubo and cli
print(f"sesiones con 3 o más respuestas: {sesiones}")
print(f" pasaron de 200,000 tokens: {grandes}")
print(f" con una pausa de 54 min o más por encima de 200,000: {con_pausa}")
print(f"vueltas tras esa pausa: {len(vueltas)}")
print(f" entre 54 min y 3 h: {sum(54 <= m < 180 for m, *_ in vueltas)}")
print(f" entre 3 h y 12 h: {sum(180 <= m < 720 for m, *_ in vueltas)}")
print(f" más de 12 h: {sum(m >= 720 for m, *_ in vueltas)}")
print(f" con la caché casi toda reconstruida: {sum(v[1] for v in vueltas)}")
print(f" en la terminal (cli): {sesiones_cli} sesiones, {sum(v[2] for v in vueltas)} vueltas")
print(f" en la app de escritorio: {con_pausa - sesiones_cli} sesiones, {sum(not v[2] for v in vueltas)} vueltas")2. Hooks que bloquean si fallan
Un hook es un comando que corre solo en un momento fijo. Hasta ahora, si ese comando no arrancaba, se pasaba de tiempo o salía con un error raro, Claude Code seguía como si nada. Para un hook que avisa, eso está bien. Para uno que te protege, es un hueco.
Desde la 2.1.295, del 8 de octubre, un hook de comando o HTTP puede llevar "onFailure": "block": si falla, la acción se frena en vez de pasar.
Fuentes: changelog oficial de Claude Code, 2.1.295; Classmethod, 9 oct 2026
.claude/settings.json (ejemplo)
{
"hooks": {
"PreToolUse": [
{ "matcher": "Bash",
"hooks": [
{ "type": "command",
"command": "python3 .claude/hooks/no-borrar.py",
"onFailure": "block" }
] }
]
}
}Un detalle: onFailure solo mira si el hook arrancó, si se pasó de tiempo o si salió con un código que no esperaba. Si tu script falla por dentro y aun así termina con 0, pasa igual. El guardia tiene que fallar bien.
Para decidir si un hook lleva block, me hago tres preguntas:
- ¿Corre antes de la acción? Un guardia que corre después ya no puede frenar nada.
- Si falla y nadie se entera, ¿qué pasa? Si la respuesta es que se borra algo o se sube algo que no debía, es un guardia.
- Si falla, ¿prefiero parar o seguir? Si prefiero seguir, lo dejo sin onFailure.
Lo primero que hice
Revisar qué tengo. Esto es lo que salió en mi Mac:
claude(--version)
2.1.294 (Claude Code)
python3(contar-hooks.py ~/.claude/settings.json (script mío))
hooks 7 onFailure 0
El claude de mi terminal estaba en la 2.1.294: no tenía ninguno de los dos cambios. Primero toca actualizar.
Después, los 7 hooks. Esto hace cada uno:
| Hook | Cuándo corre | Qué hace | ¿Guardia? |
|---|---|---|---|
| al-grano-activate | Al abrir una sesión | Carga mis reglas para que las respuestas sean cortas | No |
| al-grano-tracker | En cada mensaje mío | Me recuerda esas reglas | No |
| session-size-hook | En cada mensaje mío | Avisa si la conversación pasa de 150 peticiones | No, avisa |
| session_bridge | Al terminar cada respuesta | Guarda notas de la sesión en mi bóveda | No |
| pane-status-hook (3) | Al terminar, al avisar y al pedir permiso | Marca el estado de la sesión en mi panel | No |
Ninguno de esos 7 es un guardia. Si uno se cae, prefiero seguir trabajando, así que se quedan sin onFailure. El día que agregue uno que proteja una carpeta o frene un borrado, ese sí lleva block.
El que más se parece a lo de este post es session-size-hook. A las 150 peticiones me avisa que cierre y abra otra sesión, porque cada petición relee toda la conversación. Es regla mía desde el 9 de octubre y es un aviso, no un bloqueo: prefiero decidir yo.
La pregunta para cada hook es una sola: si este falla, ¿prefiero que el trabajo siga o que se pare?
Lo que me falta
- Actualizar el claude de mi terminal, que sigue en la 2.1.294.
- Averiguar si la app de escritorio aplica la compactación en reposo. Ahí pasan 460 de las 470 vueltas.
- Repetir el recuento con el script, ya con la versión nueva, y comparar.
Revisa lo tuyo
Pega esto en Claude Code. Solo lee, no cambia nada:
Dime qué versión de Claude Code tengo, si mis settings tienen idleCompaction configurado y cuáles de mis hooks no tienen onFailure. Para cada hook dime si es un guardia (debería bloquear si falla) o un registro (puede fallar sin parar el trabajo). No cambies nada.
En la respuesta, mira cuatro cosas:
- La versión. Antes de la 2.1.295 no existe onFailure, y antes de la 2.1.299 no hay compactación en reposo.
- idleCompaction. Si no aparece en tus settings, no lo configuraste: la función se comporta como venga por defecto en tu versión.
- Los hooks sin onFailure. Para un registro es lo normal.
- Los que Claude marca como guardia y no tienen block. Ese es el primero que arreglas.


