Skip to content

Foreground Bash tool output renders raw terminal escape sequences (breaks tmux scrollback) #2915

Description

@hmgle

Description

Foreground Bash tool stdout/stderr is rendered without sanitizing terminal
control sequences, both while output is streaming and in the final result.

If captured output contains sequences such as ESC[?1049h, pi-tui emits those
bytes to the host terminal. Inside tmux, ESC[?1049h switches the pane to the
alternate screen, making native scrollback unavailable. The pane remains in
that state after Kimi Code exits because no matching ESC[?1049l is emitted.

Reproduction

  1. Start Kimi Code inside tmux using the regular inline TUI.

  2. Ask the agent to run this exact command with the Bash tool:

    printf '\033[?1049hREPRO\n'

    This must be the agent-invoked Bash tool, not ! shell mode, whose output
    is already sanitized.

  3. From another pane, inspect the target pane:

    tmux display-message -p -t TARGET '#{alternate_on} #{history_size}'

Actual behavior

alternate_on becomes 1 (1 0 on tmux 3.7b). Native tmux scrollback is
unavailable, and the alternate-screen state persists after Kimi Code exits.

A realistic trigger is captured output from a nested TUI, for example through
script, or any command that emits raw screen-control bytes.

Expected behavior

Foreground Bash output must be treated as untrusted text and sanitized before
rendering. Rendering command output must not change the host terminal state.

Root cause

  • tool.progress stdout/stderr is forwarded to appendLiveOutput() in
    session-event-handler.ts.

  • ToolCallComponent.appendLiveOutput() accumulates the raw bytes and passes
    the complete buffer to ShellExecutionComponent.

  • Final Bash results also pass through ShellExecutionComponent.

  • ShellExecutionComponent forwards result.output to
    TruncatedOutputComponent, which applies styling and passes it to pi-tui
    Text. This path does not sanitize untrusted terminal bytes.

  • sanitizeShellOutput() already handles CSI, OSC, and other escape sequences
    for ! shell mode and background task views, but is not used here.

Suggested fix

Sanitize result.output in ShellExecutionComponent before styling and
constructing TruncatedOutputComponent. This covers both accumulated live
stdout/stderr and final Bash results.

Keep the live buffer unsanitized internally and sanitize the complete
accumulated buffer on each render. Sanitizing chunks before accumulation can
leave visible fragments when an escape sequence is split across chunks.

If sanitization is instead added to TruncatedOutputComponent, note that this
also changes rendering for generic and MCP tool results.

Regression tests

Add cases to the existing ToolCallComponent tests for:

  • live output where ESC[?1049h is split across two appendLiveOutput() calls;
  • a final Bash result containing ESC[?1049h.

Both should retain REPRO while the rendered output does not contain the
untrusted ESC[?1049h sequence.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions