CommandCodeAI / CommandCodeAI/command-code

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

未关闭
#770 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

主要语言
没有语言数据
星标
4k
派生
350
PR 合并指标
30 天内没有已合并 PR

描述

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

贡献指南

这个仓库没有索引到贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

调研方向

指定的相关入口点是 dist/cli.mjs 中的 registerInkControl()、createPhysicalLogUpdate()、physicalLineCount() 和 hardResetForResize()。在 Tauri/xterm.js PTY 中使用 CMD_FLICKER_DEBUG=1 重现,并将增量渲染与由 resize 触发的重置路径进行比较。完成的标准是 spinner 帧在该 relay 中覆盖同一行,而不是不断累积行,同时保持普通终端行为不变。

由索引模型根据 Issue 内容生成。

评估

技术栈
rust, tauri
领域
cli, desktop
Issue 类型
缺陷
难度
4/5
预计耗时
3-5 天
活跃度
活跃
描述清晰度
基本清楚
新手友好度
48/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。