Skip to content

agentHost: Simplify SSH relay reconnect with dispose-and-recreate pattern#308106

Merged
roblourens merged 1 commit intomainfrom
roblou/ssh-dispose-and-recreate
Apr 7, 2026
Merged

agentHost: Simplify SSH relay reconnect with dispose-and-recreate pattern#308106
roblourens merged 1 commit intomainfrom
roblou/ssh-dispose-and-recreate

Conversation

@roblourens
Copy link
Copy Markdown
Member

Refactor SSHConnection to use the same immutable dispose-and-recreate pattern as TunnelConnection instead of the more complex isActiveRelay()/replaceRelay() approach.

Problem

When a window reloads, we need to replace the WebSocket relay (for a fresh initialize handshake) while keeping the SSH session alive. The previous implementation mutated _relay inside a living SSHConnection and required every relay close handler to check isActiveRelay() to suppress stale close events. This was subtle and error-prone.

Solution

Adopt the pattern from TunnelConnection: connections are immutable, dispose-once objects. On reconnect, the old SSHConnection is disposed and a new one is created reusing the same SSH client.

Key changes

  • Remove isActiveRelay() and replaceRelay()SSHConnection._relay is now readonly
  • Add detachSshClient() — prevents dispose() from ending the SSH session during ownership transfer; also removes event listeners from the shared SSH client
  • Add removeListener to SSHClient interface — enables proper listener cleanup on detach
  • Simplify relay close handlers — from if (conn?.isActiveRelay(relay)) { ... } to () => { conn?.dispose(); }
  • Add error handling — try/catch in reconnect cleans up the detached SSH client if relay creation fails

Test coverage

18 new unit tests (33 total, up from 15):

  • Event lifecycle ordering
  • Relay message routing and multi-connection discrimination
  • relaySend delivery and unknown-connectionId no-op
  • Multi-host isolation (disconnect one doesn't affect other)
  • Reconnect after disconnect creates fresh SSH client
  • Progress events during connect
  • SSH client close triggers connection disposal
  • CLI install flow (skip/download)
  • Connection key formats
  • Reconnect preserves token/address
  • Reconnect failure cleanup (SSH client not leaked)
  • Listener cleanup across reconnects (close + error listener counts stable)

(Written by Copilot)

…tern

Refactor SSHConnection to use the same dispose-and-recreate pattern as
TunnelConnection instead of the more complex isActiveRelay/replaceRelay
approach.

Key changes:
- Remove isActiveRelay() and replaceRelay() from SSHConnection
- Make _relay readonly (immutable connection objects)
- Add detachSshClient() for ownership transfer during reconnect
- Add removeListener to SSHClient interface for proper cleanup
- Simplify relay close handlers to just call conn.dispose()
- Add try/catch in reconnect to clean up SSH client on failure

Add 18 new unit tests covering relay message routing, multi-host
isolation, SSH close lifecycle, CLI install flow, reconnect failure
cleanup, and listener cleanup across reconnects (33 total, up from 15).
Copilot AI review requested due to automatic review settings April 6, 2026 23:37
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors SSH relay reconnection in SSHRemoteAgentHostMainService to follow an immutable “dispose-and-recreate” connection model (matching the existing tunnel flow), avoiding in-place relay replacement and stale-close suppression logic.

Changes:

  • Make SSHConnection immutable w.r.t. its relay and add detachSshClient() to transfer SSH client ownership during relay recreation.
  • Update relay close handling to dispose the active SSHConnection (and suppress close effects from superseded connections via map-identity checks).
  • Expand the SSH remote agent host unit tests to cover reconnect behavior, relay message routing, listener cleanup, and failure cleanup.
Show a summary per file
File Description
src/vs/platform/agentHost/node/sshRemoteAgentHostService.ts Implements dispose-and-recreate relay reconnect, adds SSH client listener detachment, and simplifies relay close handling.
src/vs/platform/agentHost/test/node/sshRemoteAgentHostService.test.ts Adds/updates unit tests for reconnect semantics, relay routing, event ordering, and SSH client listener cleanup/leak prevention.

Copilot's findings

  • Files reviewed: 2/2 changed files
  • Comments generated: 0

@roblourens roblourens marked this pull request as ready for review April 6, 2026 23:46
@roblourens roblourens merged commit 2bfd243 into main Apr 7, 2026
23 checks passed
@roblourens roblourens deleted the roblou/ssh-dispose-and-recreate branch April 7, 2026 00:03
@vs-code-engineering vs-code-engineering bot added this to the 1.116.0 milestone Apr 7, 2026
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