@@ -241,26 +241,42 @@ func (p *SSHProxy) handleChannel(ctx context.Context, newChannel ssh.NewChannel,
241241 p .handleChannelRequests (serverRequests , clientChannel , sessionID , channelType , chState )
242242 }()
243243
244- // Proxy data bidirectionally with logging
245- errChan := make (chan error , 2 )
244+ clientToServerDone := make ( chan error , 1 )
245+ serverToClientDone := make (chan error , 1 )
246246
247247 // Client to Server
248248 go func () {
249249 err := p .proxyData (clientChannel , serverChannel , "client→server" , sessionID , true , chState )
250- errChan <- err
250+ // Send EOF so the remote process exits and delivers exit-status.
251+ serverChannel .CloseWrite () //nolint:errcheck
252+ clientToServerDone <- err
251253 }()
252254
253255 // Server to Client
254256 go func () {
255257 err := p .proxyData (serverChannel , clientChannel , "server→client" , sessionID , false , chState )
256- errChan <- err
258+ serverToClientDone <- err
257259 }()
258260
259- // Wait for either direction to finish or context cancellation
261+ // When client→server finishes first (SCP), wait for server→client to deliver
262+ // the response. When server→client finishes first (exec), proceed immediately
263+ // since the client→server goroutine may be blocked on stdin that never arrives.
260264 select {
261- case err := <- errChan :
265+ case err := <- clientToServerDone :
262266 if err != nil && err != io .EOF {
263- log .Debug ().Err (err ).Str ("sessionID" , sessionID ).Msg ("Channel proxy error" )
267+ log .Debug ().Err (err ).Str ("sessionID" , sessionID ).Msg ("client→server proxy error" )
268+ }
269+ select {
270+ case err := <- serverToClientDone :
271+ if err != nil && err != io .EOF {
272+ log .Debug ().Err (err ).Str ("sessionID" , sessionID ).Msg ("server→client proxy error" )
273+ }
274+ case <- time .After (3 * time .Second ):
275+ case <- ctx .Done ():
276+ }
277+ case err := <- serverToClientDone :
278+ if err != nil && err != io .EOF {
279+ log .Debug ().Err (err ).Str ("sessionID" , sessionID ).Msg ("server→client proxy error" )
264280 }
265281 case <- ctx .Done ():
266282 log .Info ().Str ("sessionID" , sessionID ).Msg ("Channel cancelled by context" )
@@ -269,7 +285,7 @@ func (p *SSHProxy) handleChannel(ctx context.Context, newChannel ssh.NewChannel,
269285 // Brief window for exit-status to be forwarded before channel teardown.
270286 select {
271287 case <- serverReqDone :
272- case <- time .After (500 * time .Millisecond ):
288+ case <- time .After (3 * time .Second ):
273289 }
274290 clientChannel .Close ()
275291 serverChannel .Close ()
0 commit comments