Summary
flicker.log
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
true inside the broken session
- CMD_FLICKER_DEBUG=1 log shows a stable
cols=95 on every STATUS_RENDER during
the broken bursts — the reported terminal width is not fluctuating or falling
back to a default.
Repro
- Run
cmd inside 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
Summary
flicker.log
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-ptyin Rust). The samepackage version works correctly in a normal terminal (tested in kitty).
Environment
portable-pty, PTY spawned with a real TTY, TERM=xterm-256color, CI unset,
TERMINFO unset for the test — none of these changed the behavior)
trueinside the broken sessioncols=95on every STATUS_RENDER duringthe broken bursts — the reported terminal width is not fluctuating or falling
back to a default.
Repro
cmdinside the embedded PTY described above.overwriting the previous frame in place.
What we found digging into dist/cli.mjs
registerInkControl()replaces Ink's internallog/throttledLogwith a customcreatePhysicalLogUpdate()renderer that does cursor-up + erase-line (viaansi-escapes'seraseLines, 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 afull
clearTerminal+ resets Ink's internallastOutput/fullStaticOutputstate.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
createPhysicalLogUpdatepath andimmediately starts stacking new lines again — even though
getCols()keepsreporting a stable, plausible
colsvalue throughout.This suggests the incremental cursor-up/erase-line path in
createPhysicalLogUpdate/physicalLineCounthas an assumption that doesn't holdin 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
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