Fix SSH agent forwarding goroutine leak via missing half-close#49
Merged
Fix SSH agent forwarding goroutine leak via missing half-close#49
Conversation
When a guest process disconnects from the agent socket, io.Copy(channel, unixConn) returns but the channel write side stays open. The host's ServeAgent blocks reading the channel waiting for a request that never comes, and the guest's reverse copy blocks waiting for a response. Neither side ever closes, leaking both goroutines and a maxAgentConns semaphore slot. After 8 leaked connections (maxAgentConns), all new agent socket connections are rejected, breaking SSH agent forwarding for the rest of the session. Symptoms: git operations succeed for the first few minutes then fail with "Permission denied (publickey)". Fix: call channel.CloseWrite() after the guest->host copy finishes, signaling EOF to the host so ServeAgent returns and the full cleanup chain completes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Verify that the agent proxy goroutines and semaphore slots are released after each session, preventing exhaustion of maxAgentConns. The TestAgentProxyCleanup test opens more sessions than maxAgentConns on a single connection, which would fail without the CloseWrite half-close fix. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
jhrozek
approved these changes
Mar 20, 2026
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.
Summary
proxyAgentConnectiondoesn't callchannel.CloseWrite()after the guest process disconnects from the agent socket. Both the guest proxy and host handler goroutines deadlock waiting on reads that will never complete, leaking amaxAgentConnssemaphore slot per connection.Permission denied (publickey).channel.CloseWrite()after the guest→hostio.Copyreturns, signaling EOF to the host soServeAgentreturns and the full cleanup chain completes.TestAgentProxyCleanupopens more sessions thanmaxAgentConnson a single connection — fails without the fix, passes with it.TestAgentProxyConcurrentcovers multiple agent queries within a single session.Test plan
TestAgentProxyCleanup— opens 10 sequential sessions (> maxAgentConns=8), verifying semaphore releaseTestAgentProxyConcurrent— multiple agent socket checks within a single sessiontask test)task lint)🤖 Generated with Claude Code