@@ -143,12 +143,12 @@ impl LogDisplay {
143143 fn finish_phase ( & mut self , phase : & str ) {
144144 self . phase = phase. to_string ( ) ;
145145 self . latest_log . clear ( ) ;
146- self . spinner
147- . finish_with_message ( format_phase_label ( & self . phase ) ) ;
148- }
149-
150- fn shutdown ( & self ) {
151- self . spinner . disable_steady_tick ( ) ;
146+ // Print the final phase as a static line above the spinner, then
147+ // clear the spinner itself. This leaves the phase label visible
148+ // in scrollback instead of erasing it with finish_and_clear().
149+ let _ = self
150+ . mp
151+ . println ( format ! ( " {}" , format_phase_label ( & self . phase ) ) ) ;
152152 self . spinner . finish_and_clear ( ) ;
153153 }
154154
@@ -1085,6 +1085,11 @@ pub async fn sandbox_create(
10851085 println ! ( " {}" , format_phase_label( phase_name( sandbox. phase) ) ) ;
10861086 }
10871087
1088+ // Don't use stop_on_terminal on the server — the Kubernetes CRD may
1089+ // briefly report a stale Ready status before the controller reconciles
1090+ // a newly created sandbox. Instead we handle termination client-side:
1091+ // we wait until we have observed at least one non-Ready phase followed
1092+ // by Ready (a genuine Provisioning → Ready transition).
10881093 let mut stream = client
10891094 . watch_sandbox ( WatchSandboxRequest {
10901095 id : sandbox. id . clone ( ) ,
@@ -1093,10 +1098,8 @@ pub async fn sandbox_create(
10931098 follow_events : true ,
10941099 log_tail_lines : 200 ,
10951100 event_tail : 0 ,
1096- stop_on_terminal : true ,
1101+ stop_on_terminal : false ,
10971102 log_since_ms : 0 ,
1098- // Only show gateway logs during provisioning — sandbox logs would
1099- // keep the stream alive indefinitely and prevent stop_on_terminal.
11001103 log_sources : vec ! [ "gateway" . to_string( ) ] ,
11011104 log_min_level : String :: new ( ) ,
11021105 } )
@@ -1106,6 +1109,8 @@ pub async fn sandbox_create(
11061109
11071110 let mut last_phase = sandbox. phase ;
11081111 let mut last_error_reason = String :: new ( ) ;
1112+ // Track whether we have seen a non-Ready phase during the watch.
1113+ let mut saw_non_ready = SandboxPhase :: try_from ( sandbox. phase ) != Ok ( SandboxPhase :: Ready ) ;
11091114 let start_time = Instant :: now ( ) ;
11101115 let provision_timeout = Duration :: from_secs ( 120 ) ;
11111116
@@ -1125,10 +1130,16 @@ pub async fn sandbox_create(
11251130 let evt = item. into_diagnostic ( ) ?;
11261131 match evt. payload {
11271132 Some ( navigator_core:: proto:: sandbox_stream_event:: Payload :: Sandbox ( s) ) => {
1133+ let phase = SandboxPhase :: try_from ( s. phase ) . unwrap_or ( SandboxPhase :: Unknown ) ;
11281134 last_phase = s. phase ;
1135+
1136+ if phase != SandboxPhase :: Ready {
1137+ saw_non_ready = true ;
1138+ }
1139+
11291140 // Capture error reason from conditions only when phase is Error
11301141 // to avoid showing stale transient error reasons
1131- if SandboxPhase :: try_from ( s . phase ) == Ok ( SandboxPhase :: Error )
1142+ if phase == SandboxPhase :: Error
11321143 && let Some ( status) = & s. status
11331144 {
11341145 for condition in & status. conditions {
@@ -1145,6 +1156,12 @@ pub async fn sandbox_create(
11451156 } else {
11461157 println ! ( " {}" , format_phase_label( phase_name( s. phase) ) ) ;
11471158 }
1159+
1160+ // Only accept Ready as terminal after we've observed a
1161+ // non-Ready phase, proving the controller has reconciled.
1162+ if saw_non_ready && phase == SandboxPhase :: Ready {
1163+ break ;
1164+ }
11481165 }
11491166 Some ( navigator_core:: proto:: sandbox_stream_event:: Payload :: Log ( line) ) => {
11501167 if let Some ( d) = display. as_mut ( ) {
@@ -1180,7 +1197,6 @@ pub async fn sandbox_create(
11801197 // Finish up - check final phase
11811198 if let Some ( d) = display. as_mut ( ) {
11821199 d. finish_phase ( phase_name ( last_phase) ) ;
1183- d. shutdown ( ) ;
11841200 }
11851201 drop ( display) ;
11861202 let _ = std:: io:: stdout ( ) . flush ( ) ;
@@ -1229,9 +1245,11 @@ pub async fn sandbox_create(
12291245 }
12301246
12311247 if command. is_empty ( ) {
1248+ eprintln ! ( "Connecting..." ) ;
12321249 return sandbox_connect ( & effective_server, & sandbox_name, & effective_tls) . await ;
12331250 }
12341251
1252+ eprintln ! ( "Connecting..." ) ;
12351253 let exec_result = sandbox_exec (
12361254 & effective_server,
12371255 & sandbox_name,
0 commit comments