Skip to content

fix mouse char bug on bwopencode exit - #23

Draft
Matt Snyder (maatthias) wants to merge 2 commits into
NSLS2:mainfrom
maatthias:fix-chars-on-exit
Draft

Matt Snyder (maatthias) wants to merge 2 commits into
NSLS2:mainfrom
maatthias:fix-chars-on-exit

Conversation

@maatthias

@maatthias Matt Snyder (maatthias) commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fix stray mouse-report characters (e.g. 51;97;30M) landing on the shell
prompt when exiting bwopencode (and the other bw* wrappers) from a GTK4
terminal such as Ptyxis over SSH.

                                   ▄     
  █▀▀█ █▀▀█ █▀▀█ █▀▀▄ █▀▀▀ █▀▀█ █▀▀█ █▀▀█
  █  █ █  █ █▀▀▀ █  █ █    █  █ █  █ █▀▀▀
  ▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀

  Session   Fix exit escape chars & enable shift+enter newline
  Continue  opencode -s ses_abcd1234

[msnyder@runansible3 ansible]$ 51;19;11M

Description

Root cause

TUI tools enable all-motion mouse tracking (ESC[?1003h) and, on exit, send
"leave alternate screen" (ESC[?1049l) before "mouse tracking off"
(ESC[?1003l).

GTK4 synthesises a pointer-motion event whenever a window's layout changes,
even if the pointer has not moved (gtk/gtkwindow.c: "This fake motion
event is needed ... when the pointer didn't move but the layout changed"
).
Leaving the alternate screen is such a layout change in Ptyxis. VTE, still in
?1003 mode at that instant, encodes the synthetic event as an SGR mouse
report (ESC[<35;x;yM, or 51 if Ctrl is still held from Ctrl-D) and sends
it back over the two SSH hops. By the time it arrives, opencode has exited.

Because launch_sandbox() exec'd bwrap, the wrapper no longer existed
either, so the report was read by the login shell: the tty echoed ^[[<,
readline consumed the escape prefix, and 51;97;30M was left on the prompt.

This is not device-specific (reproduced with no mouse attached and hands off
the trackpad) and affects any GTK4/VTE terminal (Ptyxis, GNOME Terminal,
Black Box) over a link with non-trivial latency.

Fix

lib/bwrap_sandbox_lib.sh:

  • launch_sandbox() now runs env -i ... bwrap ... as a foreground child
    instead of exec, captures the exit status and propagates it unchanged via
    exit "$_rc".
  • New _restore_terminal() runs after the child exits, only when stdin and
    stdout are a tty:
    • emits a defensive reset: mouse modes 1000/1002/1003/1006 off, focus
      events (1004) off, bracketed paste (2004) off, pop kitty keyboard flags
      (CSI < u), show cursor (all no-ops if already in the default state, so
      safe after --exec commands);
    • stty -echo, then read-and-discard stdin until it has been quiet for
      BWRAP_TTY_DRAIN_SECS (default 0.3 s, host-env overridable for
      high-latency links), with an iteration cap so a misbehaving terminal
      cannot hang the wrapper;
    • restores saved termios, falling back to stty sane if the tool crashed
      while in raw mode.
  • trap ':' INT around the wait so a Ctrl-C that kills the child does not
    also kill the wrapper before the terminal is restored. Children still
    receive SIGINT normally (trapped signals reset to default on exec).
  • print_dry_run output drops the leading exec to match the real
    invocation; comments updated accordingly.

docs/bw-wrappers.md: new "Terminal restoration on exit" section.

Side effects / notes for reviewers

  • One extra bash process per session (the wrapper stays resident).
  • --die-with-parent is now anchored to the wrapper rather than the login
    shell. Equivalent in practice: the wrapper lives exactly as long as bwrap.
  • ~0.3 s added to exit while the drain window elapses.
  • Bonus: bwcodex's trap ... EXIT that removes the staged config.toml
    (bin/bwcodex:372) never fired under exec; it will now.
  • Applies to all four wrappers (bwopencode, bwclaude, bwcodex,
    bwcopilot) since they share the library.
  • Optional follow-up: file an upstream opencode issue asking it to send
    mouse-off before leaving the alt screen (or use ?1002, which VTE dedups).
    That would fix the root cause for everyone, but the wrapper fix is still
    worthwhile as defence in depth for all four tools.

Testing

Tested with gnome/ptyxis terminal on runansible3

Need more tests on other OS/terminals

Checklist

  • Edit the sections above with relevant information
  • Update in-repo documentation, if applicable
  • Run pixi run -e dev lint locally (or wait for CI) — see .github/workflows/lint.yml
  • Test the changes on a representative managed host (or runansible deployment)

Copilot AI balanced review requested due to automatic review settings October 17, 2026 20:21
@maatthias
Matt Snyder (maatthias) marked this pull request as draft October 17, 2026 20:21

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Signal handling and background cleanup can disrupt termination or terminal state, while continued input can delay exit for minutes.

4 open issues
What changed in this PR

Prevents stray mouse reports from reaching the shell after sandboxed TUI tools exit.

Changes:

  • Keeps wrappers running to restore terminal modes and drain delayed input.
  • Adds a configurable drain timeout and documents exit cleanup.
  • Updates dry-run output to match the foreground-child launch.
File Description
lib/​bwrap_sandbox_lib.sh Adds terminal cleanup and changes sandbox launch handling.
docs/​bw-wrappers.md Documents terminal restoration and timeout configuration.

Review effort: Balanced

Comment thread lib/bwrap_sandbox_lib.sh
Comment thread lib/bwrap_sandbox_lib.sh Outdated
Comment thread lib/bwrap_sandbox_lib.sh
Comment thread lib/bwrap_sandbox_lib.sh Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The resident wrapper breaks original-caller death propagation, allowing sandbox processes to remain running.

1 open issue
4 resolved since last review

Review effort: Balanced

Comment thread lib/bwrap_sandbox_lib.sh
exec "${_ENV_BIN}" -i \
trap ':' INT
trap - TERM
"${_ENV_BIN}" -i \
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