Clear pending input when attaching a different remote terminal handle

When switching between different remote terminal handles, debounced input
meant for the previous terminal could get incorrectly flushed and sent
to the newly attached terminal.

Fix this by clearing the batcher's pending state (text and byte counts)
and invoking `inputBatcher.clear()` if the attached terminal handle
changes.
This commit is contained in:
Jinjing
2026-06-27 21:45:03 -07:00
parent 3511f368f8
commit ca4294a032
3 changed files with 53 additions and 1 deletions
@@ -50,6 +50,8 @@ export function createRemoteRuntimePtyTextBatcher(
const clear = (): void => {
clearTimer()
pending = ''
pendingBytes = 0
validationVersion += 1
validationTail = null
}
@@ -342,6 +342,50 @@ describe('createRemoteRuntimePtyTransport', () => {
expect(transport.isConnected()).toBe(false)
})
it('drops pending input when attaching a different remote terminal handle', async () => {
vi.useFakeTimers()
runtimeSubscribe.mockImplementation(
async (_args: unknown, callbacks: typeof subscriptionCallbacks) => {
subscriptionCallbacks = callbacks
return { unsubscribe: vi.fn(), sendBinary: subscriptionSendBinary }
}
)
try {
const { createRemoteRuntimePtyTransport } = await import('./remote-runtime-pty-transport')
const transport = createRemoteRuntimePtyTransport('env-1', {
worktreeId: 'wt-1',
tabId: 'tab-1',
leafId: 'pane:1'
})
transport.attach({
existingPtyId: 'remote:env-1@@terminal-old',
cols: 80,
rows: 24,
callbacks: {}
})
expect(transport.sendInput('queued-for-old')).toBe(true)
transport.attach({
existingPtyId: 'remote:env-1@@terminal-new',
cols: 80,
rows: 24,
callbacks: {}
})
runtimeCall.mockClear()
await vi.advanceTimersByTimeAsync(10)
expect(runtimeCall).not.toHaveBeenCalledWith(
expect.objectContaining({
method: 'terminal.send'
})
)
} finally {
vi.useRealTimers()
}
})
it('ignores stale attach subscription rejection after reattaching a newer remote terminal', async () => {
let rejectOldSubscription: ((error: Error) => void) | null = null
const newStream = {
@@ -507,7 +507,13 @@ export function createRemoteRuntimePtyTransport(
storedCallbacks = options.callbacks
currentRuntimeEnvironmentId =
getRemoteRuntimePtyEnvironmentId(options.existingPtyId) ?? runtimeEnvironmentId
handle = getRemoteRuntimeTerminalHandle(options.existingPtyId)
const previousHandle = handle
const nextHandle = getRemoteRuntimeTerminalHandle(options.existingPtyId)
if (previousHandle && previousHandle !== nextHandle) {
// Why: debounced input is scoped by the current terminal handle at flush time.
inputBatcher.clear()
}
handle = nextHandle
if (!handle) {
connected = false
remotePtyId = null