Cuando arreglar la fuga de tokens te rompe el scroll: el mismo interruptor

TL;DR — En Claude Code sobre VS Code dejó de funcionar el scroll con la rueda del ratón. La causa no fue un bug espontáneo: fue un parche que yo mismo había aplicado días antes para otro problema (códigos de mouse que se tecleaban solos y quemaban tokens). Resulta que el scroll y esa fuga de tokens son el mismo interruptor: CLAUDE_CODE_DISABLE_MOUSE. Al matar la fuga, maté el scroll. La solución fina fue cambiar a CLAUDE_CODE_DISABLE_MOUSE_CLICKS=1, que conserva el scroll y quita el tracking de movimiento (que era lo que fugaba). Aquí está la historia completa, reproducible.


1. El síntoma

Estaba trabajando en un plan largo dentro de Claude Code (el TUI corriendo en la terminal integrada de VS Code) y, de la noche a la mañana, ya no podía scrollear hacia arriba con la rueda del ratón para releer el plan. La rueda simplemente no movía el contenido.

En la barra inferior de la terminal aparecía este aviso, que resultó ser la pista clave:

Scroll wheel is sending arrow keys · use PgUp/PgDn to scroll

Traducido: “la rueda del ratón está enviando teclas de flecha; usa RePág/AvPág para desplazarte”. Es decir, la rueda ya no scrolleaba: se estaba convirtiendo en pulsaciones de flecha ↑↓.

El contexto que despistaba

Lo que hacía difícil el diagnóstico es que venía de otro problema previo. Días antes había pasado esto:

  • Al mover el ratón sobre la terminal, aparecían tecleados solos, en ráfaga, caracteres raros como ^[[<35;117;62M.
  • Si esa ráfaga caía dentro del prompt de Claude Code, se enviaba al modelo como si yo hubiera escrito algo → respuestas basura y consumo de tokens sin que yo tocara el teclado.
  • Para “matar” eso, apliqué un parche. Y justo después de ese parche fue cuando el scroll dejó de funcionar.

La sensación era “arreglé una cosa y se rompió otra sin relación”. Spoiler: sí estaban relacionadas. Eran, literalmente, la misma perilla.


2. Entendiendo los códigos raros (^[[<35;...M)

Antes de la solución, vale la pena entender qué eran esos caracteres, porque explican todo.

^[[<35;117;62M
  • ^[ es la tecla ESC (byte 0x1B).
  • El patrón completo es: ESC [ < Cb ; Ccol ; Cfila M.
  • Eso es un evento de mouse de xterm en modo SGR (1006).
  • Cb = 35 significa “ratón moviéndose sin botón presionado”. Los valores 0/2 serían clics de botón, y 64/65 serían la rueda arriba/abajo.

O sea: la terminal estaba en un modo donde reporta cada movimiento del ratón como una secuencia de escape. Normalmente una aplicación TUI (como Claude Code) activa ese modo mientras corre y lo apaga al salir. El problema aparece cuando la app no lo apaga — típicamente porque la sesión se cerró a la fuerza:

  • se cerró la ventana de la terminal con Claude vivo,
  • un taskkill /F sobre el proceso,
  • la terminal se congeló o crasheó.

Con el modo quedándose prendido, cualquier shell en esa consola sigue emitiendo esos eventos de movimiento como si fueran tecleados. De ahí la fuga.

Regex para detectar el síntoma (útil si quieres automatizar la detección):

\x1b\[<\d+;\d+;\d+[Mm]

3. El parche original (que causó el efecto colateral)

Para eliminar la fuga, lo que había hecho fue desactivar por completo la captura de mouse del TUI de Claude Code con una variable de entorno de usuario persistente:

[Environment]::SetEnvironmentVariable("CLAUDE_CODE_DISABLE_MOUSE","1","User")

Y como respaldo, en el perfil de PowerShell (C:\Users\rgarc\Documents\WindowsPowerShell\profile.ps1) había:

$env:CLAUDE_CODE_DISABLE_MOUSE = "1"

Más una función de rescate para “destorar” una terminal ya afectada:

function reset-term {
    $e = [char]27
    # Apaga mouse tracking (1000/1002/1003), SGR-mouse (1006), urxvt (1015),
    # focus-reporting (1004), bracketed-paste (2004) y muestra el cursor (25h).
    Write-Host -NoNewline "$e[?1000l$e[?1002l$e[?1003l$e[?1006l$e[?1015l$e[?1004l$e[?2004l$e[?25h"
    Write-Host "reset-term: reporte de mouse desactivado."
}
Set-Alias reset-mouse reset-term

Funcionó para la fuga. Pero CLAUDE_CODE_DISABLE_MOUSE=1 no apaga solo el tracking de movimiento: apaga toda la captura de mouse del TUI, incluida la rueda de scroll.


4. La causa raíz: el scroll y la fuga son el mismo interruptor

Aquí está la lección central de todo esto:

  • Con CLAUDE_CODE_DISABLE_MOUSE=1, Claude Code no captura la rueda.
  • Como Claude Code corre en la “pantalla alterna” (alternate screen), VS Code no tiene un buffer que scrollear ahí, así que traduce la rueda a teclas de flecha ↑↓.
  • Por eso el aviso: “Scroll wheel is sending arrow keys”.

Los dos problemas tienen arreglos opuestos, y eso es lo que confunde:

Problema Dónde ocurre Causa Arreglo
Se tecleaban ^[[<35;...M solos En el shell, tras cerrar Claude a la fuerza Mouse tracking quedó ENCENDIDO Apagar el mouse (reset-term)
No hace scroll la rueda Dentro del TUI de Claude Mouse tracking está APAGADO (la env var) Encender el mouse

El parche resolvió el primero de forma permanente… y con ello activó el segundo. Misma perilla, efectos opuestos.


5. La solución: conservar el scroll, matar solo el movimiento

La clave estaba en no usar el interruptor “todo o nada”. Claude Code tiene una variable más granular:

# Conserva el scroll, quita clics y tracking de movimiento (lo que fugaba):
[Environment]::SetEnvironmentVariable("CLAUDE_CODE_DISABLE_MOUSE_CLICKS","1","User")

# Borrar la variable vieja "todo o nada":
[Environment]::SetEnvironmentVariable("CLAUDE_CODE_DISABLE_MOUSE",$null,"User")

¿Por qué esto es el mejor de ambos mundos?

  • La rueda (eventos Cb=64/65) vuelve a scrollear dentro de Claude. ✅
  • Se quita el tracking de movimiento (Cb=35), que era exactamente lo que generaba la fuga ^[[<35;...M al mover el ratón. ✅
  • Los clics se desactivan, pero para un flujo de trabajo de teclado eso no estorba.

Cambios exactos aplicados

  1. Registro de usuario (persistente): borrada CLAUDE_CODE_DISABLE_MOUSE, creada CLAUDE_CODE_DISABLE_MOUSE_CLICKS=1. Verificado.
  2. profile.ps1 actualizado para limpiar el var viejo si se hereda y poner el nuevo:
    Remove-Item Env:\CLAUDE_CODE_DISABLE_MOUSE -ErrorAction SilentlyContinue  # limpia el de sesión heredado
    $env:CLAUDE_CODE_DISABLE_MOUSE_CLICKS = "1"                                # respaldo
    
  3. reset-term se conserva como red de seguridad para cierres a la fuerza.
  4. (Actualización 2026-07-12) La variable quedó además en el bloque env de ~/.claude/settings.json — ver la sección siguiente; esa es ahora la vía principal y el registro/perfil quedan de respaldo.

5.1. La vía canónica: hacerlo global en settings.json

Al validar el cambio descubrí que el registro + profile.ps1 tienen huecos: el perfil solo aplica a terminales PowerShell (no cmd ni Git Bash), y VS Code cachea el entorno al arrancar. Claude Code tiene su propio mecanismo global que evita todo eso: un bloque env en C:\Users\rgarc\.claude\settings.json:

{
  "env": {
    "CLAUDE_CODE_DISABLE_MOUSE_CLICKS": "1"
  }
}

Con esto, toda sesión de claude arranca con la variable puesta, sin importar desde qué shell la lances ni qué entorno haya cacheado VS Code. Es la forma recomendada; las otras dos capas no estorban y quedan como respaldo.


6. Cómo activarlo (detalle importante)

Las variables de entorno se leen al arrancar el proceso. La sesión de Claude que ya estaba abierta seguía con el valor viejo, así que el cambio no aplica en caliente. Para activarlo:

  1. Salir limpio de Claude: Ctrl+D (o Ctrl+C dos veces). Nunca cerrar la ventana con la sesión viva.
  2. Cerrar VS Code por completo y reabrirlo (VS Code cachea el entorno al arrancar; reabrir refresca el registro y elimina rastros del var viejo).
  3. Abrir una terminal nueva y correr claude. La rueda ya scrollea.

Alternativa sin cerrar VS Code entero: abrir una terminal nueva y correr claude. El profile.ps1 corregido limpia el var viejo heredado y pone el nuevo. Cerrar/reabrir VS Code es solo la vía a prueba de balas.

La trampa al validar: ventana nueva ≠ proceso nuevo

Aquí caí al intentar comprobar el fix: abrí una ventana nueva de VS Code esperando ver el cambio… y nada. La razón: en Windows, “File → New Window” no crea un proceso nuevo — la ventana se cuelga de la primera instancia de VS Code que abriste, y hereda el entorno que esa instancia cacheó al arrancar. Si tenías ventanas abiertas desde antes del cambio, la “nueva” nace con el entorno viejo. Para que una ventana de VS Code vea variables de entorno frescas hay que cerrar todas las ventanas primero (matar el proceso completo).

Con el bloque env de settings.json (sección 5.1) esta trampa deja de importar: Claude Code se pone la variable a sí mismo al arrancar, herede lo que herede de VS Code. Basta salir de la sesión vieja (Ctrl+D) y volver a correr claude en la misma terminal.

Cómo saber que funcionó

La prueba es dentro del TUI, no en el shell: la rueda del ratón scrollea el contenido y ya no aparece el aviso “Scroll wheel is sending arrow keys” en la barra de la terminal. Que “no pase nada raro” al mover el ratón también es éxito: la fuga ^[[<35;...M solo se manifestaba tras cerrar una sesión a la fuerza.

Mientras tanto, dentro de una sesión que arrancó con el valor viejo, para leer hacia arriba se usa PgUp / PgDn (o Shift+PgUp).


7. Referencia rápida de variables

Variable Efecto ¿Rueda scrollea? ¿Frena la fuga por movimiento?
CLAUDE_CODE_DISABLE_MOUSE=1 Apaga todo el mouse del TUI ❌ No ✅ Sí (pero rompe scroll)
CLAUDE_CODE_DISABLE_MOUSE_CLICKS=1 Quita clics + movimiento, mantiene scroll ✅ Sí ✅ Sí ← el que usamos
CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN=1 Fuerza modo clásico (sin pantalla alterna) (depende) —

Si tu versión de Claude Code es vieja y no reconoce alguna variable, no hace daño: simplemente se ignora. (Probado en Claude Code 2.1.178, Windows 11 Pro, Windows PowerShell 5.)


8. Rescate manual (si una terminal queda “atorada”)

Si algún día cierras Claude a la fuerza y una terminal empieza a teclear códigos raros al mover el ratón, en el prompt de PowerShell (no dentro del TUI):

reset-term
# equivalente manual:
$e=[char]27; Write-Host -NoNewline "$e[?1000l$e[?1002l$e[?1003l$e[?1006l$e[?1015l$e[?1004l$e[?2004l$e[?25h"

Lo más confiable en Windows sigue siendo cerrar esa pestaña y abrir una nueva.


9. Lecciones (lo que me llevo)

  1. Un “arreglo” puede ser la causa del siguiente bug. Cuando algo se rompe “de la nada”, revisa primero lo último que tocaste, aunque parezca no relacionado.
  2. Cuidado con los interruptores “todo o nada”. DISABLE_MOUSE apagaba movimiento, clics y scroll de un golpe. Casi siempre existe una perilla más fina (DISABLE_MOUSE_CLICKS).
  3. Los mensajes de la propia herramienta valen oro. El aviso “Scroll wheel is sending arrow keys” prácticamente resolvió el caso.
  4. Higiene de cierre importa. Salir de Claude con Ctrl+D/Ctrl+C (nunca cerrar la ventana en caliente) evita que el modo de mouse quede prendido y cause la fuga.
  5. Documenta el porqué, no solo el qué. Guardé el diagnóstico para no volver a caer en la misma confusión — de ahí salió este post.

Entorno probado: Windows 11 Pro (10.0.26200) · Windows PowerShell 5 · VS Code (terminal integrada) · Claude Code 2.1.178 (Opus 4.8).

Cómo recibir notificaciones por WhatsApp cuando alguien quiere acceder a tu contenido premium (Hugo + Netlify + CallMeBot)
Older post

Cómo recibir notificaciones por WhatsApp cuando alguien quiere acceder a tu contenido premium (Hugo + Netlify + CallMeBot)

Aprende a construir un sistema de paywall con blur en Hugo que notifica directo a tu WhatsApp cuando alguien solicita acceso. Sin servidores, sin mensualidades, 100% en tu sitio estático.

Newer post

Auto-compact al 50% y una barra verde de contexto bajo el prompt

Configuración global de Claude Code (Windows 11) para compactar la conversación al 50% del contexto y ver, en vivo, una barra verde con el uso, el modelo, la carpeta y el costo de la sesión.

Auto-compact al 50% y una barra verde de contexto bajo el prompt
raul@wesoft: ~/ficha