MoonshotAI / MoonshotAI/kimi-code
Foreground Bash tool output renders raw terminal escape sequences (breaks tmux scrollback)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 7.5k
- Forks
- 1.2k
- Avg merge
- 11h 53m
- Merged PRs (30d)
- 350
Description
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
-
Start Kimi Code inside tmux using the regular inline TUI.
-
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. -
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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with session-event-handler.ts, ToolCallComponent.appendLiveOutput(), and ShellExecutionComponent to trace live and final Bash output into TruncatedOutputComponent. Read the existing sanitizeShellOutput() use and ToolCallComponent tests, then add coverage for split live escape sequences and final Bash output; done means REPRO remains visible without the untrusted escape sequence in either rendering path.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- cli, testing-qa
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100