Skip to content

fix: recover ssh sessions after reconnects#2666

Open
janburzinski wants to merge 5 commits into
mainfrom
jan/eng-1633-macos-turning-off-remote-connections-freezes-agent-interface
Open

fix: recover ssh sessions after reconnects#2666
janburzinski wants to merge 5 commits into
mainfrom
jan/eng-1633-macos-turning-off-remote-connections-freezes-agent-interface

Conversation

@janburzinski

Copy link
Copy Markdown
Collaborator

Description

  • fixes freezes when ssh connections are lost or turned off
  • adds a 15s timeout for ssh pty channel opens
  • detaches stale ssh terminals and agent sessions on disconnect
  • automatically restores terminals and conversations after reconnect

Screenshot/Recording (if applicable)

https://streamable.com/8ryd6b

Checklist
  • I kept this PR small and focused
  • I ran a self-review before opening this PR
  • I ran the relevant local checks or explained why not
  • I updated docs when behavior or setup changed
  • I added or updated tests when behavior changed, or explained why not
  • I only added comments where the logic is not obvious
  • I used Conventional Commits for commit
    messages and, when possible, the PR title

@greptile-apps

greptile-apps Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds SSH session recovery to both SshConversationProvider and SshTerminalProvider: on a disconnect event, stale PTY sessions are detached and killed; on reconnect they are automatically rehydrated using saved terminal sizes. It also adds a 15-second timeout to openSsh2Pty to prevent indefinite freezes on hung SSH channel opens, capturing the specific Client instance before calling execPty so the timer can't destroy a freshly-reconnected client.

  • ssh2-pty.ts: New CHANNEL_OPEN_TIMEOUT_MS = 15 000 guard with a clientSnapshot capture before execPty to ensure the timer destroys only the stalled client, never a reconnected one.
  • SshConversationProvider / SshTerminalProvider: Both subscribe to sshConnectionManager 'connection-event' in their constructors; on 'disconnected' they call detachStaleSessionsForReconnect (saving last size, killing stale PTYs), and on 'connected'/'reconnected' they call rehydrate. The detached flag plus supervisor.acceptSpawn / post-openSsh2Pty guards ensure in-flight opens that race against detachAll don't leak live PTYs.
  • ssh-git.ts: Four LiveModel.compute lambdas are wrapped in liveModelResult so SSH errors are surfaced as err(...) values rather than uncaught promise rejections.

Confidence Score: 5/5

Safe to merge. The reconnect/rehydrate flow is well-guarded: PTY leaks on races between detachAll and in-flight openSsh2Pty calls are blocked by the detached flag in terminal providers and by supervisor.acceptSpawn token checking in the conversation provider.

All three concurrency hazards in this class of change are handled: (1) a fresh client destroyed by a stale timeout — blocked by the clientSnapshot capture; (2) PTYs spawned after detachAll — blocked by detached flag + acceptSpawn token mismatch; (3) rehydrate running after detachAll — blocked by both this.detached early-returns and isDesired returning false once supervisor.stop has been called. The new tests cover each scenario. The liveModelResult refactor in ssh-git.ts is a straightforward error-wrapping change with no new behaviour.

No files require special attention.

Important Files Changed

Filename Overview
apps/emdash-desktop/src/main/core/pty/ssh2-pty.ts Adds a 15 s channel-open timeout with a pre-captured client snapshot so the timer destroys only the stalled connection, never a reconnected one. settled flag prevents double-resolution. Synchronous throw from execPty is caught and returned as an err value.
apps/emdash-desktop/src/main/core/conversations/impl/ssh-conversation.ts Adds disconnect/reconnect handling via sshConnectionManager; detachStaleSessionsForReconnect clears sessions and saves sizes, rehydrate re-opens them. The detached flag + supervisor.acceptSpawn guard correctly prevent leaked PTYs when detachAll races a pending openSsh2Pty.
apps/emdash-desktop/src/main/core/terminals/impl/ssh-terminal-provider.ts Mirrors the conversation-provider pattern: detachStaleSessionsForReconnect on disconnect, rehydrate on reconnect. Explicit if (this.detached) guard after openSsh2Pty ensures PTYs are killed if detachAll races in-flight opens.
apps/emdash-desktop/src/main/core/conversations/conversation-session-supervisor.ts New detachActive method clears active and spawnInFlight without touching desired, letting rehydrate pass the isDesired guard while still preventing duplicate spawns via the acceptSpawn token check.
apps/emdash-desktop/src/main/core/runtime/legacy/ssh-git.ts Wraps LiveModel.compute lambdas in liveModelResult so SSH errors are returned as err(...) values rather than unhandled promise rejections, routing them through the existing onError callbacks.
apps/emdash-desktop/src/main/core/workspaces/workspace-factory.ts Single-line addition passing connectionId to SshConversationProvider constructor, which was the missing plumbing needed for the reconnect handler to filter events by connection.

Reviews (3): Last reviewed commit: "fix(ssh): cancel rehydrate after detach" | Re-trigger Greptile

Comment thread apps/emdash-desktop/src/main/core/pty/ssh2-pty.ts
@janburzinski

Copy link
Copy Markdown
Collaborator Author

@greptileai

@janburzinski

Copy link
Copy Markdown
Collaborator Author

@greptileai

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.

1 participant