fix(ssh): a Disconnect or Remove during a background connect sticks (#29918)

A background connect (managed tunnel, census, runtime context) that was
waiting for the old transport to close resumed after the user's
Disconnect or Remove host and registered a fresh connection. Re-check the
attempt token after that await so the cancelled attempt never registers.
This commit is contained in:
Neil
2026-10-16 19:32:51 -07:00
committed by GitHub
parent 776c791554
commit 03be5bb9b8
2 changed files with 29 additions and 0 deletions
@@ -169,6 +169,30 @@ describe('SshConnectionManager', () => {
expect(manager.getConnection(target.id)).toBe(replacement)
})
it('keeps a Disconnect that lands while a background connect closes the old transport', async () => {
const manager = new SshConnectionManager({ onStateChange: vi.fn() })
await manager.connect(target)
const old = mockState.instances[0]
// The pooled transport dropped, so a background dial (tunnel, census) replaces it.
old.status = 'disconnected'
let finishOldClose!: () => void
old.disconnect.mockImplementationOnce(
() =>
new Promise<void>((resolve) => {
finishOldClose = resolve
})
)
const background = manager.connect(target)
const rejected = expect(background).rejects.toThrow('cancelled')
await manager.disconnect(target.id)
finishOldClose()
await rejected
expect(mockState.instances).toHaveLength(1)
expect(manager.getConnection(target.id)).toBeUndefined()
})
it('leaves a connection whose own reconnect is asking the user alone', async () => {
const manager = new SshConnectionManager({ onStateChange: vi.fn() })
const conn = await manager.connect(target)
+5
View File
@@ -63,6 +63,11 @@ export class SshConnectionManager {
if (existing) {
this.retainSignIn(target.id, existing)
await existing.disconnect()
// Why: a Disconnect or Remove while the old transport closed cancels this attempt;
// registering now would revive the host the user just let go.
if (this.connectingTargets.get(target.id) !== attempt) {
throw new Error(`Connection to ${target.label} was cancelled`)
}
}
const conn = new SshConnection(target, this.callbacks, this.takeRetainedSignIn(target))