Skip to content

fix(attach): own the terminal state across attach and detach#23

Merged
aksOps merged 1 commit into
mainfrom
fix/attach-terminal-state
Jun 11, 2026
Merged

fix(attach): own the terminal state across attach and detach#23
aksOps merged 1 commit into
mainfrom
fix/attach-terminal-state

Conversation

@aksOps

@aksOps aksOps commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Problem

Two rendering bugs in the native attach client (post-#22):

  1. Session residue after quitting uam — the attach client never owned a screen: the host replay opens with \x1b[2J on the primary buffer (Bubble Tea leaves its alt screen before tea.ExecProcess runs the child), destroying the user's shell content; quitting uam then revealed session output instead of the shell.
  2. Corrupted TUI after in/out of a session — detach restored only termios, so modes the agent enabled mid-attach (mouse tracking, kitty keyboard — Claude Code pushes it, bracketed paste, hidden cursor, charsets) stayed active; Bubble Tea re-asserts only its own modes.

tmux attach prevented both by bracketing the session in its own alternate screen and resetting modes on detach.

Fix

  • On a tty, enter our own alt screen before the replay; the session never touches the primary screen.
  • One sync.Once restore on every exit path (detach, session end, and external SIGINT/SIGTERM/SIGHUP — now handled like a detach): mode-reset suffix → leave alt screen → restore termios.
  • Stop and drain the host→terminal pump before restoring, so buffered bytes land inside the alt screen, never on the restored primary.
  • Piped (non-tty) attaches stay byte-identical (asserted).

Test plan

  • New e2e tests over a real PTY pair through a real session host: alt-screen bracketing + ordering (TestAttachOwnsTerminalStateOnTTY), drain-before-restore under a 2000-line output burst (TestAttachDetachDrainsPumpBeforeScreenRestore); non-tty assertion added to the piped test.
  • Full suite + -race green; vet/lint/gosec/govulncheck clean.

The native attach client was a verbatim byte bridge with no terminal
ownership: the host replay opens with \x1b[2J on whatever screen the
terminal shows, and detach restored only termios. Attaching from the TUI
therefore painted the session over the user's PRIMARY screen
(tea.ExecProcess leaves the alt screen before the child runs), so
quitting uam revealed session output instead of the shell; and any modes
the agent enabled mid-attach (mouse tracking, kitty keyboard, bracketed
paste, hidden cursor, charsets) stayed active on the real terminal,
corrupting the TUI that resumes after detach.

Own the window the way `tmux attach` did:
  - on a tty, enter our own alternate screen (\x1b[?1049h) before the
    replay, so the session never touches the primary screen
  - on every exit path (detach, session end, and SIGINT/SIGTERM/SIGHUP,
    now handled like a detach) emit a mode-reset suffix - kitty pop +
    zero, mouse modes and focus reporting off, bracketed paste off,
    synchronized output off, DECSTR, keypad and charset reset, cursor
    visible - then leave the alt screen, then restore termios, all under
    one sync.Once
  - stop and drain the host-to-terminal pump before the restore, so
    bytes still buffered from the socket land inside the alt screen,
    never on the restored primary

Piped (non-tty) attaches stay byte-identical, asserted by test. Verified
end to end on a real PTY pair through the session host.
@sonarqubecloud

Copy link
Copy Markdown

@aksOps aksOps merged commit 5e8b814 into main Jun 11, 2026
13 checks passed
@aksOps aksOps deleted the fix/attach-terminal-state branch June 11, 2026 08:03
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.

1 participant