TUI command output should render bare carriage-return progress updates as terminal-visible state

Open 💬 0 comments Opened Jul 11, 2026 by starSumi

What issue are you seeing?

Codex TUI command-output rendering currently treats bare carriage returns inside captured command output as ordinary text instead of terminal cursor movement. Many progress emitters write repeated updates as one terminal line, for example:

Progress 10%\rProgress 20%\rProgress 100%\nDone

When that output is rendered from the captured command output, the UI can show a noisy single logical line containing the intermediate progress states instead of the terminal-visible final state.

This is related to, but distinct from, #23790. That issue is about preventing tools from emitting noisy progress/control output in the first place. This report is about fail-soft rendering after such output has already been captured.

Environment

  • Repository: openai/codex
  • Local draft base: origin/main at dffe1f02a3c4849478c4f412a69d25af2e6b9359
  • Freshness refresh: origin/main was re-fetched at

203d530cc6919d60f182f07d1c4c06d33809cf04; none of the local draft's
touched files changed on origin/main since the draft base.

  • Platform used for local validation: Windows 11, PowerShell
  • Scope inspected: codex-rs/ansi-escape and codex-rs/tui/src/exec_cell/render.rs

Why this narrows the root cause

The TUI exec renderer currently builds preview lines with str::lines() and then passes each line to ansi_escape_line(). Rust str::lines() handles \n and \r\n, but it does not model a bare \r as "return to column 0". That means terminal-progress output can reach ratatui as a single logical line containing carriage-return mutations.

The narrower rendering boundary is command output, not user Markdown or all ANSI text. User-authored text can legitimately contain escaped control examples, while captured command output is terminal-like stream data.

Expected behavior

Captured command output should render the same logical line state a terminal would show for common progress-output patterns:

  • bare \r returns to column 0
  • \r\n remains a normal line ending
  • ESC[K after a carriage return can clear stale tail text
  • ordinary output without \r stays on the existing rendering path

Local fix direction

I prepared a local draft patch that adds a small command-output line normalizer in codex-ansi-escape and uses it from the TUI exec output preview/transcript path:

  • codex-rs/ansi-escape/src/lib.rs
  • codex-rs/ansi-escape/tests/terminal_output_lines.rs
  • codex-rs/tui/src/exec_cell/render.rs

The helper keeps ordinary output on str::lines(), but when bare carriage returns are present it applies a small terminal-line state machine before ansi_escape_line() runs.

Local validation

Completed:

cargo fmt --all -- --config imports_granularity=Item
git diff --check HEAD~1..HEAD
cargo check -p codex-ansi-escape
cargo test -p codex-ansi-escape --test terminal_output_lines -- --nocapture

The focused test suite covers:

  • repeated carriage-return progress updates collapse to the final visible state
  • CRLF remains normal line endings
  • \r plus ESC[K clears stale tail text
  • plain output remains on the standard line path

Not completed locally:

cargo test -p codex-tui output_lines_ -- --nocapture

That command was blocked before reaching the TUI tests by the local v8 v149.2.0 build script. On this machine, Deno 2.9 rejects the build script's deno eval --allow-net invocation, and the Python/curl fallback did not complete.

Manual TUI check

I also ran a local patched Codex TUI build in Windows Terminal with an isolated
state database for the test. The manual check covered:

  • progress output using repeated bare \r
  • ordinary CRLF output
  • \r plus ESC[K clear-line output
  • transcript/raw view after completion
  • narrow terminal resize
  • copy/select sanity

The preview, transcript/raw view, resize, and copied visible text all matched
the expected terminal-visible output.

Requested maintainer action

Please confirm whether this belongs in the command-output rendering path, as a small terminal-line normalizer before ratatui line rendering, or whether the preferred direction is to keep all such handling closer to unified exec capture/sanitization.

A code patch is available locally if maintainers want it, but I am not opening
a PR unless maintainers explicitly invite one, per docs/contributing.md.

View original on GitHub ↗