Skip to content

fix(cli): only exit alternate screen buffer on SIGINT if it was entered#245

Open
jrvb-rl wants to merge 2 commits into
mainfrom
fix/ssh-wait-cursor-jump
Open

fix(cli): only exit alternate screen buffer on SIGINT if it was entered#245
jrvb-rl wants to merge 2 commits into
mainfrom
fix/ssh-wait-cursor-jump

Conversation

@jrvb-rl

@jrvb-rl jrvb-rl commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Summary

  • The global SIGINT handler in cli.ts unconditionally sent \x1b[?1049l (exit alternate screen buffer) on every Ctrl+C, even when no TUI was active.
  • Terminals interpret that sequence by restoring their last saved cursor position — jumping the cursor to the top of the screen and leaving all plain-text output (e.g. the rli d ssh provisioning wait loop) rendered below the restored cursor.
  • Fix: track whether enterAlternateScreenBuffer() was actually called in a boolean in screen.ts, and guard the SIGINT handler so it only exits the buffer when needed.

Root cause

rli d ssh dbx_<id> goes through the plain Commander path — it never calls enterAlternateScreenBuffer(). Pressing Ctrl+C while waiting for a provisioning devbox fired the SIGINT handler which always wrote \x1b[?1049l. If the terminal had a stale saved-cursor position from a prior TUI session, that escape sequence restored it, jumping the cursor and garbling the output.

Test plan

  • rli d ssh <id> against a provisioning devbox: confirm Ctrl+C exits cleanly with the cursor at the bottom of the output, no jumping.
  • rli (interactive TUI): confirm Ctrl+C still exits the alternate screen buffer and restores the normal terminal view.
  • rli d pty <id>: same check as TUI.

🤖 Generated with Claude Code

The global SIGINT handler unconditionally sent \x1b[?1049l even when no
TUI was active (e.g. `rli d ssh` waiting for a provisioning devbox).
Terminals restore their saved cursor position on that sequence, so Ctrl+C
during a plain-text wait loop jumped the cursor to the top of the screen
and left output garbled.

Track whether the alternate screen buffer is actually active in screen.ts
and guard the SIGINT handler so it only exits the buffer when needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@jrvb-rl jrvb-rl requested review from dines-rl and sid-rl June 19, 2026 08:17
@jrvb-rl jrvb-rl marked this pull request as ready for review June 19, 2026 08:17
@jrvb-rl jrvb-rl enabled auto-merge (squash) June 19, 2026 08:17
Comment thread src/utils/screen.ts
let _inAlternateScreenBuffer = false;

/** Returns true if the alternate screen buffer is currently active. */
export function isInAlternateScreenBuffer(): boolean {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe worth adding a small test to verify isInAlternateScreenBuffer() starts as false, is true after enterAlternateScreenBuffer(), and false after exitAlternateScreenBuffer()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added tests/__tests__/utils/screen.test.ts covering false → true → false around enter/exit. (ac87f8b)

Comment thread src/utils/screen.ts

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should also move, or document a follow-up to move, benchmark-job watch to use these helpers (right now it does its own handling of the raw escape sequences, but that also means isInAlternateScreenBuffer() is false when we're in the benchmark-job watch alternate screen buffer -- potential footgun)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Migrated benchmark-job watch to the shared helpers (replaced its raw enterAltScreen/exitAltScreen/cursor/clear sequences). isInAlternateScreenBuffer() now reports true while in the watch alt-screen, so the global SIGINT handler restores it correctly. (ac87f8b)

…job watch to shared helpers

Add a test verifying isInAlternateScreenBuffer() transitions false -> true ->
false around enter/exit. Migrate benchmark-job watch off its own raw escape
sequences to the shared screen helpers so isInAlternateScreenBuffer() correctly
reports the alt-screen state, letting the global SIGINT handler restore the
screen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants