62ed5a1c08
The v2 client (0.2.0) forwards its own stdin and only closes the ssh stdin channel when that
stdin hits EOF. When sic is invoked with an inherited stdin that never closes (a pipe held open
by the caller -- exactly how non-interactive tooling runs it), the channel stays open, and
pumpStdin's `io.Copy(child.stdin, ssh-stdin)` stayed BLOCKED on the read long after the remote
command had already exited. sicd never reaped, ssh never returned, and the whole call hung until
stdin EOF or a timeout. Measured: `sic host true </dev/null` = 3.4s (== raw ssh), bare = 20s hang.
Fix: race the stdin copy against the child's exit (what ssh itself does).
* child exits first -> it did not need the rest of stdin; stop forwarding, abandon the read,
return the child's status. (the hang fix)
* copy ends first (EOF/EPIPE/error) -> its outcome is meaningful; a genuine read error that
truncated the transfer to a still-live child is still a non-zero failure.
* near-simultaneous -> a bounded 50ms window still catches a real truncation, but a genuinely
blocked stdin can no longer re-introduce the hang.
Daemon-only change; the client is untouched, and v1/v2 dual-read is unaffected. New regression
TestPumpStdinReturnsOnChildExitDespiteBlockedStdin (never-EOFing stdin + exited child -> prompt
child status, not a hang). Existing pump tests (read-error->1, EPIPE head-semantics, clean EOF)
unchanged and green. Verified live: bare `sic host true` 20s -> 3.9s; piped/redirected stdin
still forward.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>