fix(cli): stop OSC 11 bg probe from trapping users in a stray editor (#35441)

Over SSH the OSC 11 background-color query round-trip routinely exceeds
the 100ms read budget, so _query_osc11_background() gives up and the late
reply lands after prompt_toolkit has grabbed the tty. prompt_toolkit then
injects the OSC payload as typed text and reads its BEL terminator
(\x07 = Ctrl+G) as a keystroke — Ctrl+G is the open-external-editor
binding, dropping the user into vi with garbage and no obvious way out.

- Skip the OSC 11 probe on remote sessions (SSH_CONNECTION/CLIENT/TTY);
  fall back to COLORFGBG / env hints / the dark default.
- Restore the tty with TCSAFLUSH instead of TCSANOW so any partial/late
  reply is scrubbed from the input buffer before pt reads it.
This commit is contained in:
brooklyn!
2026-05-30 11:55:12 -05:00
committed by GitHub
parent 6a72af044c
commit 5921d66785
2 changed files with 36 additions and 2 deletions
+12 -1
View File
@@ -1542,9 +1542,17 @@ def _query_osc11_background() -> str | None:
Most modern terminals reply with \x1b]11;rgb:RRRR/GGGG/BBBB\x1b\\
within a few ms. We wait up to 100ms total before giving up.
Returns "#RRGGBB" or None on timeout / non-tty.
Skipped over SSH: the round-trip routinely exceeds our 100ms budget, so a
late reply lands after prompt_toolkit has grabbed the tty its payload
leaks in as typed text and the BEL terminator reads as Ctrl+G (open
editor), trapping the user in a stray editor. Remote sessions fall back to
COLORFGBG / env hints / the dark default instead.
"""
if not sys.stdin.isatty() or not sys.stdout.isatty():
return None
if any(os.environ.get(v) for v in ("SSH_CONNECTION", "SSH_CLIENT", "SSH_TTY")):
return None
try:
import termios
import tty
@@ -1592,8 +1600,11 @@ def _query_osc11_background() -> str | None:
r, g, b = norm(m.group(1)), norm(m.group(2)), norm(m.group(3))
return f"#{r:02X}{g:02X}{b:02X}"
finally:
# TCSAFLUSH discards any unread input as it restores the original
# attributes — scrubs a slow/partial OSC 11 reply out of the tty
# buffer before prompt_toolkit can read it as keystrokes.
try:
termios.tcsetattr(fd, termios.TCSANOW, old)
termios.tcsetattr(fd, termios.TCSAFLUSH, old)
except Exception:
pass