Skip to content

fix(terminal): deliver escape sequences to ConPTY conin atomically (backspace types a space after resume)#9396

Open
OrcaWin wants to merge 1 commit into
mainfrom
fix/conpty-conin-write-atomicity
Open

fix(terminal): deliver escape sequences to ConPTY conin atomically (backspace types a space after resume)#9396
OrcaWin wants to merge 1 commit into
mainfrom
fix/conpty-conin-write-atomicity

Conversation

@OrcaWin

@OrcaWin OrcaWin commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

Fixes the recurring "backspace types a space in my terminal after I come back + resume sessions" report (native Windows, daemon ConPTY panes; prior partial fix: #9259).

Root cause (empirically established on Windows 11 26200)

ConPTY's input parser does not keep VT parser state across conin write boundaries. Measured with a raw-stdin child under a real ConPTY:

  • \x1b[?997;1 + n written as two writes (any gap, including immediate) → the head is silently swallowed and the app receives literal "n". Deterministic, 3/3.
  • A bracketed-paste end marker split as …\x1b[ + 201~ → the paste never closes app-side. In Claude Code's composer this leaves paste-buffering stuck: subsequent keystrokes buffer invisibly and Backspace stops deleting / inserts a blank-rendering literal — exactly the field symptom (the reporter's live session history log shows caret-advance-with-no-glyph per keypress, then self-healing after a full composer redraw).
  • The same corruption class covers every escape-shaped injection Orca legitimately writes into conin: query auto-replies (DA1/CPR/OSC color/XTVERSION DCS), mode-2031 color-scheme reports, focus reports, paste markers. Resume storms (multi-workspace reattach, the reporter had 5 sessions created in ~80s) are when these injections cluster.

Fix

ConinAtomicSequenceWriter, wired into the daemon Session write path for win32 sessions only:

  • passes through any write that ends parser-clean unchanged (every ordinary keystroke/paste — zero added latency, no copies);
  • holds a trailing partial escape sequence and joins its continuation into one atomic conin write;
  • flushes a held bare ESC after 15 ms (a real Escape keypress; never fused with a following independent key, so ESC+Backspace cannot become Alt+Backspace);
  • flushes other dangling partials after 150 ms (degrades to pre-fix behavior, breadcrumbed);
  • caps held state at the shared 4 KB partial-tail limit (reuses terminal-partial-escape-tail, the scanner already trusted on the output path);
  • drops held state on session exit/teardown.

Reliability contract

  • Touched class: terminal-input.windows-conpty-write-atomicity (new manifest gate, experimental, wired commands).
  • Invariant: every escape sequence written to a ConPTY conin is contained in a single write.
  • Failure source: live field repro (this report), prior fix(terminal): filter focus reports after Codex history resume #9259, ConPTY behavior measured in conin-atomic-sequence-writer.conpty.integration.test.ts.
  • Product change type: product runtime hardening + diagnostics + gate.
  • Oracle: byte-level — a raw-stdin child under a real ConPTY receives the joined sequence with the guard and the corrupted literal tail without it (the integration file keeps the raw defect as a pinned red so a future ConPTY fix is visible).
  • Provider/platform matrix: Windows daemon: covered (unit + real-ConPTY integration). WSL: shares the daemon write path, no dedicated live run (accepted gap). macOS/Linux: guard not constructed, write granularity untouched (covered by test). SSH: input never traverses local conhost — unaffected. Mobile/relay: reach the same daemon write path once on the host — covered by the same guard.
  • Performance budget: pass-through fast path for parser-clean writes (no timer armed, no string copy beyond the existing concat of an empty held tail); scanner is O(len) over keystroke-sized inputs. No polling, no per-key session listing, no hidden-pane work.
  • Diagnostics: env-gated breadcrumbs coninJoinedSplitSequence / coninFlushedDanglingPartial (ORCA_DAEMON_STREAM_BACKLOG_FILE) to identify the upstream split producer in the field.

Verification

  • vitest run src/main/daemon/conin-atomic-sequence-writer.test.ts src/main/daemon/session.test.ts — 84+ tests green (join/hold/flush/ESC-disambiguation/cap/dispose, win32 session routing, non-win32 granularity untouched).
  • vitest run src/main/daemon/conin-atomic-sequence-writer.conpty.integration.test.ts — green on Windows 11 26200 against a real ConPTY: documents the raw defect and proves guarded whole-sequence delivery.
  • End-to-end against a real Claude Code composer under ConPTY: with the guard, a split-paste write pattern leaves the composer healthy (pastedab → 2× Backspace → pasted); unguarded, the same pattern reproduces the stuck-composer corruption (timing-dependent, matching the intermittent field behavior).
  • pnpm run typecheck:node and oxlint clean. Pre-existing unrelated failure on this Windows host: osc7-file-uri.test.ts (fails identically on clean HEAD).

Residual gaps

  • The upstream producer of split/dangling sequences in the field is not yet pinpointed; the guard neutralizes the class at the last hop and the breadcrumbs are designed to attribute it.
  • Renderer→daemon input uses a fire-and-forget notify that silently drops whole messages while disconnected/mid-respawn; a dropped paste message is unrecoverable by this guard (follow-up candidate).
  • A ~15 ms hold on bare ESC keypresses (below perception threshold; demotion rule covers regressions).

…ackspace-types-space after resume)

ConPTY's input parser drops VT state at conin write boundaries: an escape
sequence split across two writes loses its head (silently swallowed) and
delivers its tail to the foreground app as literal keystrokes. A split
bracketed-paste end marker leaves TUIs (Claude Code composer) stuck in
paste mode, where every keystroke buffers invisibly and Backspace inserts
a blank-rendering literal — the field report 'backspace types a space
after resuming sessions'.

Add a daemon-side conin sequence-atomicity guard (win32 sessions only):
hold a trailing partial escape sequence, join its continuation into one
atomic write, flush bare ESC after 15ms (real Escape keypress) and other
partials after 150ms, cap held state at 4KB, and emit env-gated
breadcrumbs (coninJoinedSplitSequence / coninFlushedDanglingPartial) so
field data can pinpoint the upstream split producer.

Registers reliability gate terminal-input.windows-conpty-write-atomicity
with a real-ConPTY red/green oracle.
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c4747d0b-1514-405d-995e-5455e31f65aa

📥 Commits

Reviewing files that changed from the base of the PR and between 927ca9e and 22011e8.

📒 Files selected for processing (6)
  • config/reliability-gates.jsonc
  • src/main/daemon/conin-atomic-sequence-writer.conpty.integration.test.ts
  • src/main/daemon/conin-atomic-sequence-writer.test.ts
  • src/main/daemon/conin-atomic-sequence-writer.ts
  • src/main/daemon/session.test.ts
  • src/main/daemon/session.ts

📝 Walkthrough

Walkthrough

Adds ConinAtomicSequenceWriter to buffer partial escape sequences across writes, flush them using separate bare-escape and partial-sequence timers, and discard pending state during disposal. Windows Session instances route subprocess input through the writer and clean it up during teardown or exit. New unit, session, and Windows ConPTY integration tests cover sequence joining, timeout behavior, raw delivery, and platform-specific routing. A reliability gate registers the associated test commands and files.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and clearly describes the main change: atomic ConPTY escape-sequence delivery on Windows.
Description check ✅ Passed The description is detailed and covers summary and testing, but it does not follow the required template sections like Screenshots, AI Review Report, or Security Audit.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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.

3 participants