Skip to content

fix(hub): re-dial SSH-pull agents when a connection silently dies - #2126

Open
TowyTowy wants to merge 1 commit into
henrygd:mainfrom
TowyTowy:fix/2041-hub-redial
Open

fix(hub): re-dial SSH-pull agents when a connection silently dies#2126
TowyTowy wants to merge 1 commit into
henrygd:mainfrom
TowyTowy:fix/2041-hub-redial

Conversation

@TowyTowy

@TowyTowy TowyTowy commented Jul 9, 2026

Copy link
Copy Markdown

📃 Description

On the SSH-pull connection model, a per-system SSH connection could go half-open (a dead peer that never sends RST/FIN) or an agent could accept the session but never write a response. The hub's data read (fetchDataViaSSH → runSSHOperation) had no deadline, so it blocked forever. Because StartUpdater runs update() synchronously on its ticker, the blocked read froze the whole per-system goroutine — the ticker's later ticks were dropped, no error was returned (so the system stayed "up"), and the agent was never re-dialed until the hub process was restarted.

This matches #2041: agents silently drift to "dead-but-up", the hub logs no disconnect/redial, restart temporarily fixes everything, and which agents stall reshuffles between cycles.

Fix

  • Bound each SSH data exchange with a timeout (runWithTimeout). On timeout the connection is torn down (unwinding the blocked read) and a retryable error is returned, so the next collection tick re-dials. The bound (20s) is kept under the 60s collection interval so a stalled agent recovers within one cycle.
  • Enable TCP keep-alive on dialed SSH connections as a backstop so the OS eventually detects a genuinely dead idle peer.

🪵 Changelog

🔧 Fixed

Fixes #2041

The per-system updater reused an SSH client across ticks and ran the data
exchange with no deadline. If a connection went half-open (dead peer that
never sends RST/FIN) or an agent accepted the session but never wrote a
response, the read in fetchDataViaSSH blocked forever. Because
StartUpdater calls update() synchronously on its ticker, a blocked read
froze the whole per-system goroutine: the ticker's subsequent ticks were
dropped, no error was returned so the system stayed "up", and the agent
was never re-dialed until the hub process restarted.

Bound each SSH data exchange with sshOperationTimeout via runWithTimeout:
on timeout the connection is torn down (unwinding the blocked read) and a
retryable error is returned, so the next tick re-dials. Also enable TCP
keep-alive on dialed connections as a backstop for genuine network death.

Fixes henrygd#2041

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@TowyTowy
TowyTowy requested a review from henrygd as a code owner July 9, 2026 09:24
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.

[Bug]: Some SSH-pull agents silently go dead per-agent; hub never re-dials until restart (v0.18.7)

2 participants