# Logs enormes en Claude Code: los límites de Context Diet al conservar errores

> Canonical: https://vxi.me/es/posts/claude-code-context-diet-log-compression/
> Locale: es-es
> Published: 2026-10-11
> Updated: 2026-10-11
> Markdown: https://vxi.me/es/posts/claude-code-context-diet-log-compression.md

Cuando ejecutas `npm test` o `pytest` desde Claude Code, cientos de líneas `PASS` pueden ocupar la conversación mientras el único fallo importante queda sepultado. El agente vuelve a ejecutar la prueba o relee todo el log para buscarlo. Mostrar solo el final tampoco garantiza nada: la excepción puede estar en medio.

Encontré [Context Diet en Hacker News](https://news.ycombinator.com/item?id=50024608). Es un Mod para Claude Code que resume salidas largas de herramientas y deja una ruta para recuperar el resultado completo. La idea me pareció sensata: convertir el texto que ve el modelo en un índice, en lugar de obligarlo a releer ruido. Sin embargo, en el README aparece la promesa de conservar «every failure». ¿Cómo cabe cualquier cantidad de errores distintos dentro de un resumen con un límite fijo de caracteres?

Para comprobarlo fijé la revisión en [v0.3.0, commit `689284a`](https://github.com/drkokorev/context-diet/tree/689284a893f1aa509b9c6c1c06b91ef1ca52279c), leí el hook y los parsers e hice pruebas con logs artificiales sobre una adaptación manual a JavaScript del reductor genérico.

Conviene dejar claro el alcance: **no instalé ni ejecuté Context Diet como Mod dentro de Claude Code; tampoco hice pruebas A/B con el modelo.** Los números de este artículo proceden de casos sintéticos del algoritmo extraído del código. No son una tasa real de pérdida de errores del complemento ni una medida de ahorro de tokens.

## Qué acorta el hook y cómo se recupera el original

En el código fijado, el [hook `session.append`](https://github.com/drkokorev/context-diet/blob/689284a893f1aa509b9c6c1c06b91ef1ca52279c/plugins/context-diet/hooks/register.tsx) procesa resultados suficientemente largos de Bash, Grep, Glob y respuestas MCP de tipo JSON. Read, Edit, Write, WebFetch y texto MCP que no sea JSON quedan fuera de esta ruta de compresión. El umbral inicial es de **8.000 caracteres**, no de 8.000 tokens.

El mecanismo no aplica un `tail` indiscriminado. Los [parsers del código fuente](https://github.com/drkokorev/context-diet/blob/689284a893f1aa509b9c6c1c06b91ef1ca52279c/plugins/context-diet/hooks/tools.ts) reconocen formatos de Jest, Vitest, pytest, Go test, Cargo y comprobadores de TypeScript. Dan prioridad a fallos, ubicaciones y contexto frente a pruebas superadas. JSON conserva un esqueleto, mientras que diffs, grep y listas de rutas siguen otras reglas. Son **estrategias comprobadas leyendo el código**; yo no ejecuté todos esos frameworks dentro de una sesión real de Claude Code.

La protección que más me interesa es el camino de vuelta. Antes de sustituir el resultado, el hook intenta guardar el texto completo en `.context-diet/out-NNN.txt` e incluye su ruta en el resumen. Hay **300 ranuras rotativas**. Si falla la escritura, deja pasar el resultado original, en vez de anunciar una ruta que no existe.

Pero guardar en disco no significa archivar para siempre. Las ranuras antiguas se reutilizan y los logs pueden contener claves, variables de entorno y rutas privadas. `.gitignore` reduce la posibilidad de subir esos archivos a Git; no cifra datos ni hace copias de seguridad. Para investigar un incidente de producción, conserva el log original por otro medio.

## Dar prioridad al error no equivale a conservarlos todos

Al revisar [`dietLog` en `diet.ts`](https://github.com/drkokorev/context-diet/blob/689284a893f1aa509b9c6c1c06b91ef1ca52279c/plugins/context-diet/hooks/diet.ts), encontré una secuencia razonable: agrupar líneas repetitivas, reservar parte del espacio para el inicio y el final, y buscar ventanas de errores desde atrás intentando incluir su contexto.

Lo decisivo es esta condición:

```ts
const cost = window.reduce((n, i) => n + (folded[i]?.text.length ?? 0) + 1, 0)
if (used + cost > target) continue
```

Si una ventana de error no cabe en el presupuesto restante, se omite. Con un solo fallo suele funcionar bien; con decenas de fallos distintos y trazas grandes, el resumen simplemente no puede incluirlo todo.

Jest y pytest reconocidos utilizan parsers especializados con otros criterios, también limitados. **Mi experimento mide únicamente la ruta genérica de fallback para logs, no la capacidad real de los parsers específicos de Jest o pytest.** No sería correcto transformar una proporción sintética en «el plugin descarta el 95 % de errores de Jest».

La interpretación defendible de «every failure» es que se *prioriza* la información de fallos y se conserva un acceso al original, no que cada línea de cada error vaya a aparecer siempre en un fragmento de tamaño limitado.

## Un error en el medio, y luego 145 errores diferentes

No quise montar otra instalación de Claude Code solo para observar una restricción de tamaño. Adapté manualmente el reductor genérico `dietLog` y sus funciones de texto a JavaScript sin dependencias y lo ejecuté con Node.js v22.16.0 en un entorno aislado. La batería sintética más completa terminó con **4/4 pruebas aprobadas y código de salida 0**. No invocó la API del Mod.

| Entrada artificial | Texto original | Resumen | Observación |
| --- | ---: | ---: | --- |
| Log largo con un fallo central | 32.087 caracteres | 1.175 caracteres | se conserva 1/1 marcador |
| 145 errores distintos, objetivo de 1.450 caracteres | 23.146 caracteres | 1.419 caracteres | visibles 7/145 líneas originales de error |
| Salida corta | 45 caracteres | 45 caracteres | sin cambios |
| Control `PASS errorHandler.test.ts` | 52 caracteres | 52 caracteres | PASS no se clasifica como error |

En el segundo caso, **138 líneas originales de error no aparecían en el resumen visible**. Este es un fragmento del resultado *sintético* realmente guardado. No es la traza de un fallo ocurrido en una aplicación real:

```text
… [225 lines cut] …
ERROR CASEei unhandled category_ts in /app/modules/amy/workflow.ts:40 expected stable result bci
ERROR CASEej unhandled category_tt in /app/modules/amz/workflow.ts:40 expected stable result bcj
ERROR CASEek unhandled category_tu in /app/modules/ana/workflow.ts:40 expected stable result bck
…
… [79 lines cut] …
```

Hay otro detalle fácil de pasar por alto. El reductor indicaba «5 of 145 error lines with context», pero al contar todas las líneas originales de error visibles en el texto final encontré **siete**. El inicio o el final, conservados por separado, también pueden contener errores. «Ventanas de error elegidas» y «líneas de error que se ven» no son la misma métrica.

**Los 1.450 caracteres fueron un presupuesto deliberadamente reducido para esta prueba sintética; no son el valor predeterminado del plugin.** En v0.3.0, el umbral que activa la compresión es de **8.000 caracteres**. Para una salida larga normal, el objetivo de resumen se calcula con `Math.max(2_000, Math.round(threshold / 2))`: **4.000 caracteres** con el umbral inicial. Cuando Claude Code ya guardó la salida en un archivo, el objetivo es de **3.000 caracteres**. Un presupuesto mayor puede mostrar más detalles, pero este experimento no permite inferir ningún porcentaje real de errores omitidos.

Preparé además una reproducción mínima con **dos pruebas y solo la biblioteca estándar de Node.js**, centrada en el selector por presupuesto. Sus líneas artificiales de error son algo más largas: conserva **8/145** con el mismo objetivo de 1.450 caracteres. Pasaron ambas pruebas (**2/2**). El caso de siete y este de ocho tienen entradas distintas; no hay que tratarlos como una comparación de rendimiento.

Este es el código completo de la reproducción pequeña. Guárdalo como `budget-replay.test.mjs` y ejecuta `node --test budget-replay.test.mjs`:

```js
/**
 * First-party negative control for Context Diet v0.3.0's generic dietLog budget path.
 * Pin: drkokorev/context-diet @ 689284a893f1aa509b9c6c1c06b91ef1ca52279c.
 * Only the error-window budget logic is mirrored, for controlled ASCII log lines.
 * This is NOT an upstream Mod invocation, a benchmark, or a production loss rate.
 * The full adapted dietLog fixture and observed digests are in the separate evidence bundle.
 */
import assert from 'node:assert/strict';
import test from 'node:test';

const id = i => { let s='',n=i+1; while(n){ n--;s=String.fromCharCode(97+n%26)+s;n=Math.floor(n/26); } return s; };
const noise = n => Array.from({length:n},(_,i)=>'step '+id(i)+' completed '+id(i+1200)+' '+i);
const critical = line => /^ERROR\b|^\s+(Expected|Received):/.test(line);
function select(raw,target){
  const folded=raw.split('\n').map(text=>({text,level:critical(text)?2:0}));
  const errorHits=folded.map((l,i)=>l.level===2?i:-1).filter(i=>i>=0);
  const keep=new Set();let used=0;
  const headBudget=target*(errorHits.length?0.1:0.2);
  const tailBudget=target*(errorHits.length?0.25:0.35);
  for(let i=0;i<folded.length&&used<headBudget;i++){keep.add(i);used+=folded[i].text.length+1;}
  let tailUsed=0;
  for(let i=folded.length-1;i>=0&&tailUsed<tailBudget;i--){
    if(!keep.has(i)){keep.add(i);tailUsed+=folded[i].text.length+1;}
  }
  used+=tailUsed;
  let windowsShown=0;
  for(let h=errorHits.length-1;h>=0;h--){
    const at=errorHits[h];const window=[];
    for(let i=at-2;i<=at+3;i++)if(i>=0&&i<folded.length&&!keep.has(i))window.push(i);
    const cost=window.reduce((n,i)=>n+folded[i].text.length+1,0);
    if(used+cost>target)continue; // EXACT budget guard from upstream dietLog.take()
    window.forEach(i=>keep.add(i));used+=cost;windowsShown++;
  }
  return {visible:folded.filter((_,i)=>keep.has(i)).map(l=>l.text).join('\n'),totalCritical:errorHits.length,windowsShown};
}
test('single late/central failure remains visible',()=>{
  const text=[...noise(310),'ERROR ECASEONE impossible transaction transition','  Expected: completed','  Received: pending',...noise(310)].join('\n');
  const out=select(text,2000);
  assert.ok(out.visible.includes('ECASEONE'));
});
test('many distinct failures cannot all fit finite digest budget',()=>{
  const failures=Array.from({length:145},(_,i)=>'ERROR CASE'+id(i)+' unhandled category_'+id(i+400)+' in /app/modules/'+id(i+900)+'/workflow.ts:40 expected stable result '+id(i+1300));
  const raw=[...noise(90),...failures,...noise(90)].join('\n');
  const out=select(raw,1450);
  const retained=failures.filter(line=>out.visible.includes(line)).length;
  assert.ok(retained>0&&retained<failures.length);
  process.stdout.write(JSON.stringify({failure_lines:failures.length,retained,missing:failures.length-retained,digest_budget_chars:1450,mode:'source-budget-window-control-not-plugin-runtime'})+'\n');
});
```

El ejemplo solo reproduce la restricción de selección del log genérico. No llama a la API de Mods de Claude Code. Que las aserciones pasen no demuestra el funcionamiento del plugin instalado ni el comportamiento del agente al corregir errores.

La conclusión no es que comprimir sea una mala idea. El resumen debe seguir siendo **un índice hacia la evidencia completa, no la evidencia misma**.

## Empieza con dry; decide capture por separado

Si usas una versión de Claude Code compatible con Mods, contrasta el [README fijado](https://github.com/drkokorev/context-diet/blob/689284a893f1aa509b9c6c1c06b91ef1ca52279c/README.md) con la documentación actual del cliente. En esa versión el autor pedía Claude Code **2.1.287 o posterior**. No he comprobado personalmente la instalación en un cliente real.

```text
/plugin marketplace add drkokorev/context-diet
/plugin install context-diet@context-diet
```

Yo empezaría por `/diet dry` y `/diet report`. El modo dry está diseñado para calcular cuánto se recortaría sin sustituir la salida original. Para comandos cuyo log debe permanecer íntegro, usa `/diet keep <fragmento de comando>` o desactiva la función con `/diet off`.

`/diet capture on` aborda otra situación: Claude Code puede haber truncado por su cuenta una salida muy grande antes de que el hook la procese. Capture viene **desactivado**. Para ciertos comandos Bash sencillos de pruebas o compilación, modifica la ejecución, une stdout y stderr en `.context-diet/run-NN.log`, vuelve a leerlos e intenta preservar el código de salida.

Ese envoltorio puede dejar de coincidir con permisos ya autorizados, por ejemplo `Bash(pytest:*)`, y provocar una nueva confirmación. Unir dos flujos también puede alterar el orden observable. Las tuberías, las cadenas de comandos, las redirecciones existentes y los procesos en modo watch o en segundo plano quedan excluidos. No lo activaría a ciegas en flujos sensibles a permisos o que dependen de la salida en tiempo real. `/diet capture off` restaura el ajuste inicial.

El [README del autor](https://github.com/drkokorev/context-diet/blob/689284a893f1aa509b9c6c1c06b91ef1ca52279c/README.md) afirma que siete preguntas se respondieron correctamente con y sin el plugin, mientras que el total de llamadas a herramientas bajó de **13 a 8**. En otros **18 intentos de corrección por agentes**, presenta promedios de **8,0 sin**, **6,0 con** y **5,8 con capture**. Son resultados agregados declarados por el autor con una muestra pequeña; yo no reproduje las trazas, los costes ni las tasas de éxito.

Si tu conversación queda inundada por pruebas aprobadas, Context Diet merece una evaluación. Pero en diagnósticos de incidentes, errores concurrentes o decisiones de fusionar un PR, un resumen no basta para dar por completa la lista de fallos. ¿El agente volverá a leer el archivo o corregirá únicamente los fallos visibles antes de abrir una PR? Es una pregunta de comportamiento todavía sin medir en esta investigación.

Mis artículos anteriores sobre [dividir CLAUDE.md, AGENTS.md y Skills](https://aitokenhacks.com/posts/claude-md-agents-md-skills-ci/) y [transferir estado entre agentes](https://aitokenhacks.com/posts/ai-agent-orchestration-after-assistants/) trataban de qué información cargar y quién asume cada tarea. Esta vez la pregunta es más inmediata: ¿la respuesta de una herramienta que ve el modelo es la prueba completa, o una nota que permite volver a ella?
