fix(run): raw-mode-safe CRLF for launch-path status banners#13
Open
evgeny-boger wants to merge 2 commits into
Open
fix(run): raw-mode-safe CRLF for launch-path status banners#13evgeny-boger wants to merge 2 commits into
evgeny-boger wants to merge 2 commits into
Conversation
The "newer image available" banner is printed from a background task on the launch path, which can resolve *after* the interactive session has put the terminal into raw mode (crossterm::enable_raw_mode in the exec path). In raw mode the terminal's ONLCR output post-processing is off, so the bare "\n" from eprintln! is a line feed with no carriage return: the cursor drops a row but keeps its column, staircasing the second line off the end of the first. Extract the banner into a pure `update_banner(cached, remote, is_tty)` helper that terminates each line with CRLF on a TTY (rendering flush-left regardless of raw mode) and plain LF for redirected stderr (no stray carriage returns in logs/pipes). Add unit tests for both paths. Also convert the now-single-arm match to `if let` (silences clippy::single_match). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… terminator
The `--auto-publish` "watching" line and per-port event lines (`run.rs`)
are emitted from the launch path — the events from a background task during
the live, raw-mode session — using bare-`\n` `eprintln!`. Same failure mode
as the update banner: under raw mode (ONLCR off) a bare LF doesn't return the
carriage, so the line staircases.
Extract the CRLF-vs-LF decision into a shared `status_line_ending(is_tty)`
helper (the "shared raw-mode-safe writer" the earlier review flagged),
have `update_banner` use it, and switch the three auto-publish messages to
`eprint!("…{nl}")` with `nl = status_line_ending(stderr().is_terminal())`.
The per-event task samples is_terminal() once before its loop — a fd's
TTY-ness is stable for the process.
Add a unit test for `status_line_ending`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Status lines printed during launch staircase under raw mode — the next line is shoved to the right:
Root cause
Several launch-path messages are emitted from background tasks that can run after the interactive session has switched the terminal to raw mode (
crossterm::enable_raw_modein the exec path):notify_if_update_available), and--auto-publish"watching" line and per-port event lines.In raw mode the terminal's
ONLCRpost-processing is off, so the bare\nfromeprintln!is a line feed with no carriage return: the cursor drops a row but keeps its column.Fix
Introduce one shared helper,
status_line_ending(is_tty), that returns:\r\non a TTY — renders flush-left whether or not raw mode is active (harmless in cooked mode);\nfor redirected stderr — no stray carriage returns in logs/pipes.Route every racing launch-path status line through it:
update_banner(update-available banner), and--auto-publishmessages (eprint!("…{nl}"); the per-event task samplesis_terminal()once before its loop — a fd's TTY-ness is stable for the process).Also converts the now-single-arm
matchinnotify_if_update_availabletoif let(silencesclippy::single_match).Tests
status_line_ending_is_crlf_on_tty_and_lf_otherwiseupdate_banner_uses_crlf_on_tty_so_second_line_doesnt_staircaseupdate_banner_uses_plain_lf_off_ttycargo build,cargo test -p agent-vm(131/131 with--test-threads=1; one unrelated pre-existing flake races on a process-wide env var under parallel runs), andcargo clippy -p agent-vmall pass — zero new clippy warnings (net −1).End-to-end (real binary)
Ran the actual compiled
agent-vmbinary inside a raw-mode PTY (slave incfmakeraw, i.e. the post-enable_raw_modestate), with a planted stalepulled-digestsmarker socheck_for_updatefires a live ghcr.io probe and hitsUpdateAvailable:is_terminal()at banner time →true(stderr is the PTY).…)\r\n==> Run … cached image.\r\n— real CRLF, flush-left.This exercises
status_line_ending(stderr().is_terminal())in the real binary. The--auto-publishlines call the identical helper but could not be driven end-to-end here: they print only after a successfulSandbox::create, and this nested dev box can't boot the guest microVM (entering VM → Vmm is stopping— nested KVM is unavailable). They're covered by the shared helper's real-binary proof + the unit test + code review.🤖 Generated with Claude Code