Skip to content

Startup probe and image delivery: end on DA1, never type a late reply, resend undelivered uploads - #533

Draft
raiseCatError wants to merge 3 commits into
fix/welcome-native-still-20261029from
fix/host-probe-late-replies-20261029
Draft

raiseCatError wants to merge 3 commits into
fix/welcome-native-still-20261029from
fix/host-probe-late-replies-20261029

Conversation

@raiseCatError

@raiseCatError raiseCatError commented Oct 28, 2026 •

Copy link
Copy Markdown
Owner

Stacked on #532.

Bug (reproduced in Kitty 0.49.2 with a relay that delays the terminal's replies by 0.3 s or 1.5 s): the startup probe always waited a fixed 80 ms. A later Kitty graphics reply reached the key decoder as Escape, then the text _Gi=31;OK\, then another Escape. The text was typed into the composer, and the key press stopped the welcome as character art. In Ghostty, which has no graphics hint, the session would draw every image as character art. Any SSH session or busy Mac hits this.

Fix

  • src/terminal/TerminalRenderer.ts and src/graphics/service.ts (commit 2): sendRaw drops bytes before NMSh has entered the screen, but the service still recorded the Kitty upload. A welcome image that loaded during startup was then drawn as empty placeholder cells for the whole session. The longer probe wait in commit 1 made this race likely: it was blank at a 1.5 s delay. Undelivered uploads are no longer recorded, so the next paint sends them.
  • src/host/probe.ts: DA1 is now the last query (DISPLAY_QUERY reordered) and ends the probe as soon as it answers, so there's no fixed wait. A slow terminal gets up to 750 ms (HOST_PROBE_REPLY_TIMEOUT_MS). A timer that fires late, after a blocked event loop, first lets waiting input in. The result has pending when the end marker didn't arrive.
  • takeLateProbeReplies plus TerminalApp: for 10 s after a pending probe, late id-31 graphics replies are removed from the input, and a late OK turns native images on and repaints. The window closes when DA1 arrives.
  • src/terminal/keys.ts: a complete Kitty graphics reply (ESC _ G … ST) is never decoded as keys, and an incomplete one at the end of a read is held until it completes.

Tests

  • Commit 3 fixes a crash found in Ghostty 1.3.1 with a 1.5 s delay. A late graphics OK called renderer.setCapabilities after the renderer had entered the screen; that throws, and NMSh exited. The fix updates only the live host capabilities. Regression test in tests/hostProbeExtended.test.ts: it fails on commit 2 and passes now (33/33 related).
  • Commit 2: tests/graphicsService.test.ts (an undelivered upload is sent again) and tests/keys.test.ts (a held partial reply is flushed like Escape). Both fail without the fix.
  • Focused after commit 2: graphicsService, keys, hostProbeExtended, imageSurface, welcomePlayback, welcomeLogoAnimation, welcomeRuntime, viewImages, cmdArrow, terminalHost: 80/80.
  • Independent review: no blockers. Fixed: the unbounded held suffix, and late DA1/size replies reaching a program. Not fixed: a late graphics reply split across two reads is held (never typed), but its evidence is not used.
  • New tests in tests/hostProbeExtended.test.ts cover the end marker, the early finish, the late reply as evidence, and that the reply is never typed. tests/keys.test.ts covers a whole reply and one split across reads.
  • Focused: hostProbeExtended, keys, terminalHost, terminalHardening, hostProfiles, graphicsCapabilities, controlStringBounds, cmdArrow: 55/55.
  • imageSurface, graphicsService, queryOrder, ptyResizeLive, inputAwarenessLive: 35/35.
  • tsc is clean. The full suite is left to CI.

Physical QA (disposable Kitty, private HOME and Kitty config, Auto graphics, slow-relay.py):

Results are judged by screenshot pixels in the logo area, not by placeholder counts. Placeholder cells can be present while the picture is missing.

build no delay delay 0.3 s delay 1.5 s
c3620236 native, then character art after a key character art, _Gi=31;OK\ typed character art, _Gi=31;OK\ typed
commit 1 only native native blank (upload dropped before the screen was entered)
this PR (head) native at start, after a key and with /appearance open same same

Nothing was typed in any of the commit 2 runs.

Ghostty 1.3.1, isolated instance (--config-default-files=false, private HOME); screenshots in the contact sheet shots/gm-sheet.png:

  • c3620236: the welcome is character art even with no delay. With 0.3 s or 1.5 s it says "This terminal did not confirm a graphics protocol", and _Gi=31;OK\ is typed into the composer.
  • Commit 2: native image at start and after a key, with no delay and at 0.3 s. At 1.5 s NMSh exited; commit 3 fixes that crash.
  • Not yet checked on the head in Ghostty: the 1.5 s case, and /appearance (the QA driver's typed text was garbled in Ghostty, so those shots don't count).

Not tested: Ghostty physically, and a terminal that never answers DA1. That terminal would wait 750 ms once at startup instead of 80 ms.

… reply

The probe always waited a fixed 80 ms. A slower reply (SSH, a busy Mac)
reached the key decoder, which turned Kitty's APC graphics reply into
Escape + '_Gi=31;OK\' + Escape: the text was typed and the key stopped
the welcome as character art; in Ghostty images stayed character art for
the session.

- DA1 is the last query and ends the probe as soon as it answers; a slow
  terminal gets up to 750 ms; a timer that fires late lets waiting input in.
- Replies after that are taken out of the input for 10 s and a late
  graphics OK still enables native images.
- The key decoder never decodes a Kitty graphics reply as keys, even split.
TerminalRenderer.sendRaw drops bytes before NMSh has entered the screen
(and while suspended), but GraphicsService recorded the Kitty upload
anyway: a welcome image that loaded during startup was drawn as empty
placeholder cells for the whole session. sendRaw now reports whether it
wrote; an undelivered upload is not recorded and the next paint sends it.

Review follow-ups: a held start of a graphics reply is short (64) and is
flushed on the Escape pause like a lone ESC; a late DA1 or pixel-size
reply is removed too, so it never reaches a program that has the screen.
@raiseCatError raiseCatError changed the title Startup probe: end on DA1, wait for slow terminals, never type a late reply Startup probe and image delivery: end on DA1, never type a late reply, resend undelivered uploads Oct 28, 2026
…ered renderer

Found in Ghostty with replies delayed 1.5 s: a late graphics OK called
renderer.setCapabilities after entry, which throws, and NMSh exited.
Images read the live host capabilities, so that call was not needed.

This branch has not been deployed

No deployments
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