Skip to content

fix(apps): restore terminal on apps shell exit (stop clobbering the shell)#22

Merged
mattias-lundell merged 1 commit into
mainfrom
fix/apps-shell-terminal-restore
Jul 3, 2026
Merged

fix(apps): restore terminal on apps shell exit (stop clobbering the shell)#22
mattias-lundell merged 1 commit into
mainfrom
fix/apps-shell-terminal-restore

Conversation

@mattias-lundell

Copy link
Copy Markdown
Member

Bug

After running opper apps shell and disconnecting, the local terminal is left broken — every keypress emits CSI-u byte soup and the shell errors:

~ c9;1:3ul8;1:3uea1;1:3u7;1:3ur4;1:3u
zsh: command not found: c9
zsh: command not found: 1:3ul8

Those ;1:3u fragments are the kitty keyboard protocol (CSI-u key events). The remote PTY (ttyd + whatever ran in it) enabled it via output bytes we forward to stdout, and restore() only called setRawMode(false) — it never sent the sequences to turn those modes back off. So they persisted after disconnect and clobbered the shell. This happened even on a clean exit (the .finally() ran, but did nothing about the escape modes).

Fix

restore() now also writes the matching "disable" sequences, all no-ops on modes that weren't set:

  • \e[<u — pop kitty keyboard protocol flags (the ;1:3u garble)
  • \e[?2004l — bracketed paste off
  • \e[?1000l \e[?1002l \e[?1003l \e[?1006l — mouse reporting off
  • \e[?25h — show cursor
  • \e> — normal keypad

Also made restore() idempotent and registered it on exit / SIGINT / SIGTERM / SIGHUP, so an abrupt disconnect (Ctrl-, terminal hang-up, kill) restores the terminal too — not just the clean-exit path.

Immediate remedy if you're stuck now: run reset (or stty sane; printf '\e[<u').

Tests

tsc typecheck + build clean; full suite 381 passing. Verified the sequences compile into dist/. (The apps shell command opens a live WebSocket/PTY, so this path isn't unit-tested; the change is a focused set of known terminal-reset escapes on the existing cleanup path.)

Found live this session, right after the apps shell clobber.

🤖 Generated with Claude Code

… shell)

Exiting `opper apps shell` left the local terminal in a broken input mode:
every keypress produced CSI-u byte soup (";1:3u") and zsh spat "command not
found". Cause: restore() only did setRawMode(false). The remote PTY (ttyd +
whatever ran in it) enables terminal modes via output bytes we forward to
stdout — most painfully the kitty keyboard protocol — and nothing ever turned
them back off, so they persisted after disconnect.

restore() now also writes the matching "disable" sequences (pop kitty keyboard
flags, bracketed paste off, mouse reporting off, show cursor, normal keypad) —
all no-ops on modes that weren't set. Made it idempotent and also run it on
exit/SIGINT/SIGTERM/SIGHUP, so an abrupt disconnect (not just a clean one)
restores the terminal too.

Found by hitting the clobber live after an `apps shell` session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mattias-lundell mattias-lundell merged commit 0037c0e into main Jul 3, 2026
5 checks passed
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