pub(super) async fn answer_conpty_handshake_if_seen(
state: &AppState,
block_id: &str,
baseline_len: usize,
)Expand description
Answer Windows ConPTY’s own startup handshake, if seen, before the
caller proceeds. Discovered live (not assumed): the moment a Windows
ConPTY is created, it queries the cursor position (ESC[6n, Device
Status Report) and blocks its own internal state — and therefore every
later write — until it sees a ESC[<row>;<col>R reply. A human-facing
term pane gets this for free because xterm.js, running in the
browser, answers real cursor queries as part of being an actual
terminal emulator; this headless facility has nothing playing that
role, so without this the PTY would sit permanently stuck before
printing even its first prompt — confirmed by a live round trip that
hung indefinitely until this was added. ESC[1;1R (cursor at row 1,
col 1) is a plausible-enough fixed answer: nothing here has rendered
anything yet, so “top-left” is as true as any other coordinate, and
ConPTY only wants to reconcile its internal buffer, not validate the
value against anything a person would see.
baseline_len MUST be the “term” file’s byte length taken immediately
before resync_controller ran (0 for a genuinely new block) — see the
call sites. Only bytes appended AFTER that offset are ever inspected.
Not safe to scan the whole file regardless of reuse/fresh (an
earlier version of this function did exactly that): the “term” file is
append-only for the block’s entire lifetime
(handle_append_block_file’s FILE_OP_APPEND), so a long-running
reused shell’s history almost always still contains its own original,
long-since-answered ESC[6n from whenever it first started — matching
against that stale byte sequence would inject an unsolicited
ESC[1;1R into whatever a human is doing RIGHT NOW on that shell
(Codex P1 on PR #3194, confirmed live before this fix: create on an
already-running reused shell always found the old query and misfired).
The baseline makes every case correct uniformly: nothing new appears
(the common reuse case) → correctly finds nothing; resync_controller
had to respawn a dead process → correctly sees only ITS fresh query,
never anything from the block’s former life.