Severity: High (hangs that ignore the user's configured timeouts)
1. OPENSHELL_PROVISION_TIMEOUT is dead
WatchUntilReady (pkg/sandbox/watch.go:122-132) checks the idle deadline only after stream.Recv() returns. If the gateway goes silent, Recv blocks forever and the timeout — whose entire purpose is the silent case — never fires. (lifecycle.go:68 does this correctly with context.WithTimeout.) Fix: run Recv in a goroutine and select on a resettable timer, or cancel the stream ctx from a timer that progress events reset. While here: StepDone elapsed is always 0 because resetsIdle resets deadline before elapsed is computed (watch.go:154-160); track lastProgressAt.
2. SSH handshake has no timeout
pkg/transfer/sshconn.go:31,44,53-55: ssh.ClientConfig.Timeout only applies inside ssh.Dial; ssh.NewClientConn ignores it, and tunnelConn.SetDeadline is a no-op. A gateway that accepts the tunnel stream but never relays hangs connect/upload/download until SIGINT. Fix: run the handshake in a goroutine, select on ctx.Done() / time.After(sshDialTimeout), close rwc on timeout.
3. Connect ignores cancellation after stdout EOF
pkg/transfer/connect.go:220-228 blocks on <-reqsDone with no ctx.Done() case. A remote that closes stdout but keeps the channel open (or a lost peer) hangs, SIGTERM-proof.
(waitForDeletion swallowing non-NotFound errors is owned by item 16.)
Acceptance
- Scripted-stream test with a fake clock: no events for > idle timeout →
ErrProvisionTimeout (with GPU hint when applicable).
- In-process SSH server that never completes the version exchange →
connect returns within the configured timeout.
Connect returns on ctx cancel after stdout EOF.
Generated by Claude Code
Severity: High (hangs that ignore the user's configured timeouts)
1.
OPENSHELL_PROVISION_TIMEOUTis deadWatchUntilReady(pkg/sandbox/watch.go:122-132) checks the idle deadline only afterstream.Recv()returns. If the gateway goes silent,Recvblocks forever and the timeout — whose entire purpose is the silent case — never fires. (lifecycle.go:68does this correctly withcontext.WithTimeout.) Fix: runRecvin a goroutine andselecton a resettable timer, or cancel the stream ctx from a timer that progress events reset. While here:StepDoneelapsed is always 0 becauseresetsIdleresetsdeadlinebeforeelapsedis computed (watch.go:154-160); tracklastProgressAt.2. SSH handshake has no timeout
pkg/transfer/sshconn.go:31,44,53-55:ssh.ClientConfig.Timeoutonly applies insidessh.Dial;ssh.NewClientConnignores it, andtunnelConn.SetDeadlineis a no-op. A gateway that accepts the tunnel stream but never relays hangsconnect/upload/downloaduntil SIGINT. Fix: run the handshake in a goroutine,selectonctx.Done()/time.After(sshDialTimeout), closerwcon timeout.3.
Connectignores cancellation after stdout EOFpkg/transfer/connect.go:220-228blocks on<-reqsDonewith noctx.Done()case. A remote that closes stdout but keeps the channel open (or a lost peer) hangs, SIGTERM-proof.(
waitForDeletionswallowing non-NotFound errors is owned by item 16.)Acceptance
ErrProvisionTimeout(with GPU hint when applicable).connectreturns within the configured timeout.Connectreturns on ctx cancel after stdout EOF.Generated by Claude Code