test(terminal): pin the quarantine cap's accepted cost; correct its fresh-shell claims (STA-2373)

The idle-gap test claimed a fresh command is never eaten, but the idle gate
cannot release the first quarantined byte, so a command typed inside the cap is
dropped in full. Pin that real behavior and the reason the cap stays 5s.

A write rejection means the endpoint stopped accepting writes; the daemon
respawns only on a daemon-gone error, so a transient socket drop reconnects to
the same sessions. Stop asserting reattach always lands on a fresh shell.
This commit is contained in:
Brennan Benson
2026-07-26 18:41:24 -07:00
parent 11bdf37541
commit 3e23dddd14
3 changed files with 25 additions and 7 deletions
@@ -3638,8 +3638,9 @@ export function connectPanePty(
// cooldown window while typing. Local panes keep the lenient gate.
requireAuthoritativeLiveness:
Boolean(transport.getConnectionId?.()) || isRemoteRuntimePtyId(undeliverablePtyId),
// Why only the rejected path: a stalled-pipeline remount reattaches to the
// same shell, so its half-typed line is still on screen and intact.
// Why only the rejected path: it is the only one whose remount can land on
// a fresh shell. A stalled-pipeline remount always reattaches to the same
// shell, so its half-typed line is still on screen and intact.
endpointReplaced: providerRejected
})
}
@@ -56,7 +56,7 @@ describe('terminal input quarantine', () => {
expect(isTerminalInputQuarantined(TAB)).toBe(false)
})
it('releases on an idle gap so a fresh command is never eaten', () => {
it('releases on an idle gap once a quarantined byte has been seen', () => {
armTerminalInputQuarantine(TAB, 0)
// Burst tail, no Enter — the user was still mid-line when the endpoint died.
expect(shouldDropQuarantinedTerminalInput(TAB, 'c', REATTACH_MS)).toBe(true)
@@ -66,6 +66,22 @@ describe('terminal input quarantine', () => {
expect(isTerminalInputQuarantined(TAB)).toBe(false)
})
// The accepted cost of the cap, pinned so it is not "fixed" by shortening it:
// over-quarantine is visible (nothing echoes) and recoverable by retyping,
// while under-quarantine executes half a command. A tail takes seconds to
// type, so a cap short enough to spare this command would cut that tail
// mid-line and deliver its remainder to the fresh shell.
it('eats a fresh command typed inside the cap, terminator included', () => {
armTerminalInputQuarantine(TAB, 0)
// No byte yet, so the idle gate cannot release the first keystroke however
// long the user waited; from there normal typing never opens a 700ms gap.
let at = 1_500
for (const char of 'ls -la\r') {
expect(shouldDropQuarantinedTerminalInput(TAB, char, at)).toBe(true)
at += 150
}
})
it('does not let the idle gate fire on the re-attach delay itself', () => {
armTerminalInputQuarantine(TAB, 0)
// First quarantined byte arrives well past the idle window, because the
@@ -38,10 +38,11 @@ type RecoveryRequest = {
* registry doesn't own, and treating null as "proceed" would let a
* disconnected remote pane churn reconnects on every cooldown window. */
requireAuthoritativeLiveness?: boolean
/** The provider rejected the write because its endpoint died, so re-attach
* lands on a *fresh* shell. Only then is the in-flight line mangled, and
* only then may input be quarantined — a recovery that keeps the same live
* shell would have a legitimate command eaten. */
/** The provider rejected the write because its endpoint stopped accepting
* writes, so re-attach MAY land on a *fresh* shell (a respawn; a transient
* socket drop reconnects to the same sessions). Only this path can mangle the
* in-flight line, and only it may quarantine input — a recovery that always
* keeps the same live shell would have a legitimate command eaten. */
endpointReplaced?: boolean
}