CommandCodeAI / CommandCodeAI/command-code
Spinner/status line doesn't overwrite in-place when running inside a non-standard PTY relay (Tauri + xterm.js)
Chưa có ai nhận issue này.
- Ngôn ngữ chính
- Không có dữ liệu ngôn ngữ
- Star
- 4k
- Fork
- 350
- Chỉ số merge pull request
- Không có pull request nào được merge trong 30 ngày
Mô tả
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
Hướng dẫn đóng góp
Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Hướng nghiên cứu
Các entry point liên quan được nêu là registerInkControl(), createPhysicalLogUpdate(), physicalLineCount() và hardResetForResize() trong dist/cli.mjs. Hãy tái hiện với CMD_FLICKER_DEBUG=1 trong PTY của Tauri/xterm.js và so sánh quá trình render tăng dần với nhánh reset được kích hoạt bởi resize. Được xem là hoàn tất khi các frame của spinner ghi đè lên một dòng trong relay đó thay vì tích lũy các dòng, trong khi hành vi của terminal thông thường vẫn được giữ nguyên.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- rust, tauri
- Lĩnh vực
- cli, desktop
- Loại issue
- Lỗi
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức độ hoạt động
- Sôi nổi
- Độ rõ ràng
- Khá rõ ràng
- Mức phù hợp với người mới
- 48/100