CommandCodeAI / CommandCodeAI/command-code
Spinner/status line doesn't overwrite in-place when running inside a non-standard PTY relay (Tauri + xterm.js)
Nadie ha tomado este issue todavía.
- Lenguaje dominante
- Sin datos de lenguaje
- Estrellas
- 4k
- Forks
- 350
- Métricas de merge de PR
- Sin PR fusionados en 30 d
Descripción
Summary
Summary
The animated status line ("Preparing… esc to interrupt • Ns • ↓ 0") prints a new line
on every animation frame instead of overwriting the previous one, when Command Code
runs inside a PTY that isn't a native terminal (specifically: a Tauri desktop app
embedding xterm.js, with the shell spawned via portable-pty in Rust). The same
package version works correctly in a normal terminal (tested in kitty).
Environment
- command-code v1.38.2 (models: claude-sonnet-4.6 · taste-1)
- Host terminal: kitty (macOS)
- Reproduces inside: a Tauri app's embedded terminal tab (xterm.js 5.x +
portable-pty, PTY spawned with a real TTY, TERM=xterm-256color, CI unset,
TERMINFO unset for the test — none of these changed the behavior) - process.stdout.isTTY / process.stdin.isTTY: both
trueinside the broken session - CMD_FLICKER_DEBUG=1 log shows a stable
cols=95on every STATUS_RENDER during
the broken bursts — the reported terminal width is not fluctuating or falling
back to a default.
Repro
- Run
cmdinside the embedded PTY described above. - Ask any question that triggers the "thinking" status spinner.
- Every animation frame (~80ms cadence) appears on its own new line instead of
overwriting the previous frame in place.
What we found digging into dist/cli.mjs
registerInkControl() replaces Ink's internal log/throttledLog with a custom
createPhysicalLogUpdate() renderer that does cursor-up + erase-line (via
ansi-escapes's eraseLines, wrapped in DEC synchronized-output \x1b[?2026h/l)
instead of Ink's built-in log-update. This custom renderer is what fails to
correctly erase the previous frame in our environment.
Notably: hardResetForResize() (triggered on every SIGWINCH-driven resize) does a
full clearTerminal + resets Ink's internal lastOutput/fullStaticOutput state.
While we are actively resizing the host window (continuous SIGWINCH), every frame
goes through this hard-reset path and renders correctly. The instant resizing
stops, rendering falls back to the incremental createPhysicalLogUpdate path and
immediately starts stacking new lines again — even though getCols() keeps
reporting a stable, plausible cols value throughout.
This suggests the incremental cursor-up/erase-line path in
createPhysicalLogUpdate/physicalLineCount has an assumption that doesn't hold
in this PTY relay (order/timing of writes vs. Ink's own "Static" content stream,
possibly), while the "hard reset" path is unaffected because it doesn't rely on
tracked cursor position at all.
Attached
~/.commandcode/logs/flicker.log (CMD_FLICKER_DEBUG=1 session covering a broken burst)
Expected Behavior
While Command Code is streaming its "thinking" status (spinner + "esc to interrupt • Ns • ↓ 0"), each animation frame should overwrite the previous one in place — the status line should stay on a single line/row, not accumulate new lines.
Actual Behavior
Every animation frame (~80ms cadence) is printed as a brand-new line instead of overwriting the previous one, so the terminal fills up with dozens of stacked "Preparing… esc to interrupt • 0s • ↓ 0" / "Sculpting… esc to interrupt • 1s • ↓ 0" lines for a single status update. This only happens inside a PTY relay that isn't a native terminal (a Tauri desktop app embedding xterm.js, shell spawned via Rust's portable-pty) — the exact same cmd install works correctly in a normal terminal (tested in kitty on the same machine).
Diagnostics ruled out: process.stdout.isTTY/process.stdin.isTTY are both true; CI/CONTINUOUS_INTEGRATION are unset; TERMINFO unset makes no difference; CMD_FLICKER_DEBUG=1 shows a stable cols=95 on every STATUS_RENDER during the broken bursts (not falling back to a default, not fluctuating).
Notably: while continuously resizing the host window (which sends repeated SIGWINCH → hardResetForResize() → full clearTerminal + Ink internal state reset), the status line renders correctly — the bug only reappears once resizing stops and rendering falls back to the incremental createPhysicalLogUpdate (cursor-up + eraseLines, wrapped in \x1b[?2026h/l) path that registerInkControl() patches onto Ink's internal logger.
Steps to reproduce the issue
- Run command-code (v1.38.2) inside a PTY that is not a native terminal — specifically, a Tauri app with a shell spawned via portable-pty in Rust and rendered in the frontend with xterm.js (env: TERM=xterm-256color, real TTY, CI unset).
- Ask any question that triggers the "thinking" status spinner (e.g. "analiza en qué fase vamos").
- Observe: each spinner frame appears on a new line instead of overwriting the previous one.
- (Optional, to confirm) Run with CMD_FLICKER_DEBUG=1 cmd, reproduce, then inspect ~/.commandcode/logs/flicker.log — cols= stays stable throughout the broken burst.
- (Optional, to see the masking effect) While the status is streaming, continuously resize the host window — the spinner overwrites correctly during the resize, then reverts to stacking lines the moment resizing stops.
Command Code Version
1.38.2
Operating System
macOS
Terminal/IDE
Unknown
Shell
zsh
Session file (optional)
No response
Fix prompt (optional)
No response
Additional context
OS: macOS Tahoe 26.6.2
Guía de contribución
No hay ninguna guía de contribución indexada para este repositorio
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Línea de trabajo
Los puntos de entrada relevantes mencionados son registerInkControl(), createPhysicalLogUpdate(), physicalLineCount() y hardResetForResize() en dist/cli.mjs. Reprodúcelo con CMD_FLICKER_DEBUG=1 en el PTY de Tauri/xterm.js y compara el renderizado incremental con la ruta de restablecimiento activada por el cambio de tamaño. Se considera terminado cuando los frames del spinner sobrescriben una fila en ese relay en lugar de acumular líneas, mientras el comportamiento del terminal normal permanece intacto.
Escrito por el modelo de indexación a partir del texto del issue.
Evaluación
- Stack tecnológico
- rust, tauri
- Área
- cli, desktop
- Tipo de issue
- Error
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Estado de actividad
- Activo
- Claridad
- Bastante claro
- Aptitud para principiantes
- 48/100