Fix proxycommand hang when server closes before stdin closes#1647
Merged
Conversation
proxyDirect waited for both copy goroutines via wg.Wait(). When the server closes the connection mid-session, the stdin->conn goroutine stays blocked reading a stdin that never reaches EOF (the ssh client holds the proxycommand's stdin open until it exits), deadlocking until an OS-level timeout reaps the process. Return as soon as either direction finishes instead. Extracted a testable proxyDirectWithIO and added a regression test. Fixes #1641 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1641 · Linear: PRE-124
Problem
step ssh proxycommandhangs ~60s when the SSH server closes the connection before the client has closed stdin — e.g. when the server rejects the session mid-stream (PAM account check fails after cert auth succeeds).The deadlock is in
proxyDirect: it spawns two copy goroutines andwg.Wait()s for both. When the server closes, theconn→stdoutgoroutine returns, but thestdin→conngoroutine stays blocked readingos.Stdin— which never reaches EOF because the SSH client holds the proxycommand's stdin open until the proxycommand exits. Neither side gives, sowg.Wait()blocks until an OS-level timeout reaps the process.Fix
Return as soon as either direction finishes instead of waiting for both. For a proxycommand there's nothing left to do once one half of the SSH transport closes — returning lets the process exit and the OS reclaim the still-blocked goroutine. Replaced the
sync.WaitGroupwith a buffereddonechannel +<-done, and addeddefer conn.Close()so the surviving copy goroutine is also unblocked on the way out.Testing
Extracted a testable
proxyDirectWithIO(host, port, stdin, stdout)and addedTest_proxyDirectWithIO_serverClosesBeforeStdin: a TCP server that sends data then closes, with a stdin that never EOFs. Verified it hangs the full timeout against the old logic (RED) and returns promptly after the fix (GREEN, race-clean).go build ./...,go vet, and the fullcommand/sshpackage pass.🤖 Generated with Claude Code