CommandCodeAI / CommandCodeAI/command-code

Spinner/status line doesn't overwrite in-place when running inside a non-standard PTY relay (Tauri + xterm.js)

Đang mở
#770 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

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

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
  1. Run cmd inside the embedded PTY described above.
  2. Ask any question that triggers the "thinking" status spinner.
  3. 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
  1. 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).
  2. Ask any question that triggers the "thinking" status spinner (e.g. "analiza en qué fase vamos").
  3. Observe: each spinner frame appears on a new line instead of overwriting the previous one.
  4. (Optional, to confirm) Run with CMD_FLICKER_DEBUG=1 cmd, reproduce, then inspect ~/.commandcode/logs/flicker.log — cols= stays stable throughout the broken burst.
  5. (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

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. 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.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. 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

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.