TL;DR — Si Claude Code te muestra títulos y tablas repetidos al subir en la terminal, no estás pagando tokens doble: el texto se guarda una sola vez y lo que falla es el pintado del renderer clásico (
tui: "default") cuando una respuesta es más alta que la ventana. “Empezó con Opus 5” es cierto, pero por el tamaño de las respuestas, no por el CLI: con la misma versión 2.1.281, Opus 5.5 escribe casi el doble y con el doble de tablas que Opus 4.8. Lo único que lo elimina es la pantalla completa (/tui fullscreen); si prefieres conservar el scroll nativo, se reduce mucho maximizando la ventana y pidiéndole a Claude respuestas compactas.
1. El síntoma
Le pedí a Claude Code un informe largo (varias secciones con tablas). Al subir en la terminal vi el mismo encabezado dos veces, con un pedazo de tabla en medio. Parecía que el modelo se había repetido.
Lo curioso: con Opus 4.8 casi nunca me pasaba. Fue cambiar a Opus 5.5 y empezar a verlo en todas mis sesiones.
Contexto: en agosto yo mismo había apagado el renderer de pantalla completa para recuperar el scroll nativo de la terminal (lo conté en Cómo desactivar el renderer fullscreen de Claude Code). Ese detalle resultó clave.
2. Primera pregunta: ¿el modelo escribió doble?
No. Claude Code guarda cada sesión en ~/.claude/projects/<proyecto>/<sesión>.jsonl. Busqué el título repetido dentro del mensaje guardado:
grep -o "NO borrar todavía" ~/.claude/projects/*/<id-de-sesion>.jsonl | wc -l
Aparece una sola vez en el mensaje. La copia extra solo existe en la pantalla. Es un fallo de renderizado, no de generación (y no se cobra doble).
3. Es un bug conocido (y abierto)
En el repositorio de Claude Code hay decenas de reportes del mismo síntoma. Los más útiles:
- #52924 — duplicados en sesiones largas, Windows y Linux. Alguien probó todo de forma sistemática.
- #51828 — bisección versión por versión y la explicación del mecanismo.
- #57133, #67834 — Windows 11 + respuestas largas con tablas.
- #79896 — otro usuario revisó 12 sesiones: cero duplicados en los
.jsonl.
El mecanismo (resumido de #51828): para redibujar, el renderer clásico sube el cursor y borra lo anterior. Si la respuesta es más alta que la ventana, el cursor no puede subir más allá del borde superior; lo que ya se fue al scrollback no se puede borrar, y el redibujado deja una copia vieja encima de la nueva.
Anthropic anunció arreglos parciales en las versiones 2.1.116, 2.1.118 y 2.1.121 (redimensionar, Ctrl+L, cerrar diálogos), pero el caso “respuesta larga” sigue reproduciéndose. Revisé el changelog hasta la 2.1.310 y no hay arreglo para el modo clásico.
Lo que la comunidad probó y no sirvió: cambiar de consola a Windows Terminal, "useAtlasEngine": false, apagar el recap de sesión, cambiar de shell (PowerShell, CMD, Git Bash).
4. “Empezó con Opus 5”: lo medí
Como el texto se guarda en los .jsonl, puedo comparar modelos con datos. Este script recorre tus sesiones y mide las respuestas de texto del hilo principal (más de 200 caracteres):
// model-output-stats.mjs → node model-output-stats.mjs
import fs from 'fs'; import path from 'path'; import os from 'os';
const root = path.join(os.homedir(), '.claude', 'projects'); const S = {};
for (const d of fs.readdirSync(root)) {
const p = path.join(root, d); if (!fs.statSync(p).isDirectory()) continue;
for (const f of fs.readdirSync(p).filter(x => x.endsWith('.jsonl'))) {
for (const line of fs.readFileSync(path.join(p, f), 'utf8').split('\n')) {
if (!line.includes('"assistant"')) continue; let j; try { j = JSON.parse(line) } catch { continue }
const m = j.message?.model; if (!m || !/opus-4-8|opus-5/.test(m) || j.isSidechain) continue;
const t = (j.message.content || []).filter(c => c.type === 'text').map(c => c.text).join('\n');
if (t.length < 200) continue;
const s = S[m] ??= { n: 0, chars: 0, lines: 0, tables: 0 };
s.n++; s.chars += t.length; s.lines += t.split('\n').length;
if (/^\|.*\|\s*$/m.test(t)) s.tables++;
}
}
}
for (const [m, s] of Object.entries(S))
console.log(m, s.n, 'resp | chars', Math.round(s.chars / s.n), '| líneas', Math.round(s.lines / s.n),
'| con tabla', Math.round(100 * s.tables / s.n) + '%');
Mi resultado:
| Respuestas largas | Opus 4.8 | Opus 5.5 |
|---|---|---|
| Analizadas | 997 | 1,219 |
| Caracteres en promedio | 867 | 1,674 |
| Líneas en promedio | 10 | 20 |
| Llevan tabla | 11% | 24% |
Y el dato que cierra el caso: todas mis sesiones, con los dos modelos, corrieron en Claude Code 2.1.281. El renderer fue el mismo; lo que cambió fue cuánto tiene que pintar. Opus 5.5 rebasa la altura de la ventana mucho más seguido, y el bug, que siempre estuvo ahí, aparece mucho más.
Por eso cada salto de modelo “trae el bug de vuelta”: en GitHub hay reportes idénticos de cuando se pasó de Opus 4.6 a 4.7 (#53576, #51940).
5. ¿Dónde se forma la copia? Una prueba que no esperaba
Quise reproducirlo de forma controlada: lancé claude en modo clásico dentro de un ConPTY (la capa de pseudo-terminal de Windows, con pywinpty) a 120×30 y pasé todo lo que emitía por un emulador VT con scrollback (pyte). Luego conté cuántas veces quedaba cada fila de una tabla con marcas únicas.
| Prueba | Duplicados |
|---|---|
| Haiku, tabla de 60 filas | 0 |
| Opus 5.5, informe con 6 tablas | 0 |
| Lo mismo, redimensionando a media respuesta | 0 |
| Lo mismo, sobre una copia de una sesión larga | 0 |
Además, el flujo no traía ni CSI 2J ni CSI 3J ni bloques de synchronized output. Es decir: lo que sale de Claude Code por ConPTY llega limpio en estos casos. La copia se forma en la capa de Windows Terminal (en mi caso, la 1.24), o necesita un disparador que no simulé.
6. La prueba A/B en Windows Terminal real
Un emulador no es la terminal real, así que hice la prueba definitiva: dos ventanas de Windows Terminal de 120×30, la misma sesión (copiada con --fork-session para no tocar la original), el mismo prompt pidiendo repetir el informe largo, y sin tocar el tamaño de las ventanas.
A_clasico.cmd:
@echo off
set CLAUDE_CODE_NO_FLICKER=
set CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN=1
claude --resume <id-de-sesion> --fork-session --model claude-opus-5-5 "Repite el informe largo, con todas sus tablas."
B_fullscreen.cmd:
@echo off
set CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN=
set CLAUDE_CODE_NO_FLICKER=1
set CLAUDE_CODE_ALT_SCREEN_FULL_REPAINT=1
claude --resume <id-de-sesion> --fork-session --model claude-opus-5-5 "Repite el informe largo, con todas sus tablas."
ab.ps1 (abre las dos con una sola línea):
$dir = $PSScriptRoot
Start-Process wt.exe -ArgumentList "-w new --size 120,30 cmd /k `"$dir\A_clasico.cmd`""
Start-Sleep -Seconds 2
Start-Process wt.exe -ArgumentList "-w new --size 120,30 cmd /k `"$dir\B_fullscreen.cmd`""
Resultado:
- A (clásico): duplicó. Los encabezados y tablas del informe aparecieron repetidos al subir.
- B (pantalla completa): no duplicó, ni siquiera al volcar todo al scroll nativo con
Ctrl+Oy luego[.
7. Las soluciones, con su costo real
| Opción | ¿Quita el duplicado? | ¿Conservas el scroll nativo? |
|---|---|---|
Pantalla completa (/tui fullscreen) |
✅ Sí (validado en la A/B) | Solo a pedido: Ctrl+O y luego [ |
| Clásico + ventana maximizada + respuestas compactas | Lo reduce mucho | ✅ Completo |
| Proxies (claude-chill, Quell) | ❌ No aplican a este caso en Windows | — |
Si eliges pantalla completa:
- Escribe
/tui fullscreenen cualquier sesión (se reinicia y se queda así). - En Windows Terminal agrega
CLAUDE_CODE_ALT_SCREEN_FULL_REPAINT=1en el bloqueenvde tusettings.json, para evitar restos de texto en pantalla. - Para buscar o copiar como antes:
Ctrl+O(modo transcripción), luego/para buscar o[para mandar toda la conversación al scroll nativo. - Para volver:
/tui default.
Si, como yo, prefieres conservar el scroll nativo:
- Maximiza la ventana antes de mandar el prompt y no la redimensiones mientras escribe. El bug aparece cuando la respuesta no cabe; con más altura, cabe más seguido.
- Pídele respuestas compactas con una regla en tu
CLAUDE.md. La mía:
- **Respuestas compactas en el chat (que quepan en pantalla).** Uso el renderer clásico
(`tui: "default"`) para conservar el scroll nativo, y ese modo duplica texto cuando una
respuesta es más alta que la ventana. Por eso: respuestas cortas, idealmente ≤ ~40 líneas;
máximo una tabla y de pocas filas; listas en vez de tablas cuando se pueda. Si el contenido
es largo (informes, inventarios), escribirlo en un archivo y en el chat dar solo el resumen
y la ruta.
Esto ataca la causa que medí: si Opus 5.5 escribe el doble, que lo largo se vaya a un archivo.
Sobre los proxies: claude-chill es excelente, pero hoy solo corre en Linux y macOS (depende del crate nix; los dos PR de soporte para Windows se cerraron sin integrarse), aunque su README muestre una insignia de Windows. Quell sí es nativo de Windows, pero ataca el salto de scroll quitando CSI 2J dentro de bloques sincronizados, y el renderer clásico no emitió ninguno en mis pruebas.
8. Lo que me llevo
- Verifica dónde está el fallo antes de culpar al modelo. El
.jsonlte dice en segundos si el texto se generó doble o solo se pintó doble. - “Empezó con la actualización” merece una medición. Aquí el CLI no cambió; cambió el volumen de salida. Sin los números, la conclusión habría sido la equivocada.
- Valida en la terminal real. El emulador me dio una pista valiosa (el flujo sale limpio), pero solo la A/B en Windows Terminal confirmó la solución.