@@ -55,9 +55,9 @@ func snapshotRef(id string) *sandboxv1.SnapshotRef {
5555}
5656
5757// consumeCommandEventStream drains a depot.sandbox.v1 CommandEvent stream
58- // into stdout/stderr and returns the final exit code from Finished. The
59- // stream shape mirrors RunCommand / RunCommandPipe / AttachCommand /
60- // RunHook: Started -> Stdout/Stderr/Error/EvictedEarlyData* -> Finished .
58+ // into stdout/stderr and returns the final exit code from Finished. The stream
59+ // shape is generally Started -> Stdout/Stderr/Error/EvictedEarlyData* ->
60+ // Finished; detached RunCommand streams end cleanly after Started .
6161//
6262// EvictedEarlyData is reported on stderr as a single line so log consumers
6363// see the gap; the stream continues afterward. Error frames are surfaced the
@@ -66,13 +66,16 @@ func snapshotRef(id string) *sandboxv1.SnapshotRef {
6666func consumeCommandEventStream (
6767 stream * connect.ServerStreamForClient [sandboxv1.CommandEvent ],
6868 stdout , stderr io.Writer ,
69+ allowMissingFinished bool ,
6970) (exitCode int32 , err error ) {
7071 defer func () { _ = stream .Close () }()
72+ sawStarted := false
7173 for stream .Receive () {
7274 msg := stream .Msg ()
7375 switch ev := msg .Event .(type ) {
7476 case * sandboxv1.CommandEvent_Started_ :
7577 // metadata only — nothing to print
78+ sawStarted = true
7679 case * sandboxv1.CommandEvent_Stdout :
7780 if ev .Stdout != nil && len (ev .Stdout .Data ) > 0 {
7881 _ , _ = stdout .Write (ev .Stdout .Data )
@@ -99,6 +102,9 @@ func consumeCommandEventStream(
99102 if err := stream .Err (); err != nil && ! errors .Is (err , io .EOF ) {
100103 return 0 , fmt .Errorf ("command stream: %w" , err )
101104 }
105+ if allowMissingFinished && sawStarted {
106+ return 0 , nil
107+ }
102108 // Stream closed without a Finished event. Treat this as an error rather
103109 // than silently reporting exit 0 — a clean disconnect mid-command is
104110 // indistinguishable from a real exit-0 completion to the caller otherwise.
0 commit comments