@@ -222,6 +222,23 @@ fn sandbox_should_persist(keep: bool, forward: Option<&ForwardSpec>) -> bool {
222222 keep || forward. is_some ( )
223223}
224224
225+ fn has_main_process_result ( sandbox : & Sandbox ) -> bool {
226+ let Some ( status) = sandbox. status . as_ref ( ) else {
227+ return false ;
228+ } ;
229+ if status. exit_code . is_none ( ) {
230+ return false ;
231+ }
232+
233+ let phase = SandboxPhase :: try_from ( sandbox. phase ( ) ) . unwrap_or ( SandboxPhase :: Unknown ) ;
234+ phase != SandboxPhase :: Error
235+ || status. conditions . iter ( ) . any ( |condition| {
236+ condition. r#type == "Ready"
237+ && condition. status . eq_ignore_ascii_case ( "false" )
238+ && condition. reason == "MainProcessFailed"
239+ } )
240+ }
241+
225242fn build_sandbox_resource_limits (
226243 cpu : Option < & str > ,
227244 memory : Option < & str > ,
@@ -761,14 +778,11 @@ pub async fn sandbox_create(
761778 saw_non_ready = true ;
762779 }
763780
764- let has_main_process_result = s
765- . status
766- . as_ref ( )
767- . is_some_and ( |status| status. exit_code . is_some ( ) ) ;
781+ let main_process_result = has_main_process_result ( & s) ;
768782 if matches ! (
769783 phase,
770784 SandboxPhase :: Completed | SandboxPhase :: Error | SandboxPhase :: Stopped
771- ) && has_main_process_result
785+ ) && main_process_result
772786 {
773787 if let Some ( d) = display. as_interactive_mut ( ) {
774788 d. clear ( ) ;
@@ -856,10 +870,7 @@ pub async fn sandbox_create(
856870
857871 // If we exited the loop without hitting the Ready break, finish the display.
858872 let final_phase = SandboxPhase :: try_from ( last_phase) . unwrap_or ( SandboxPhase :: Unknown ) ;
859- let final_has_main_process_result = last_sandbox
860- . status
861- . as_ref ( )
862- . is_some_and ( |status| status. exit_code . is_some ( ) ) ;
873+ let final_has_main_process_result = has_main_process_result ( & last_sandbox) ;
863874 if !( matches ! (
864875 final_phase,
865876 SandboxPhase :: Ready | SandboxPhase :: Completed | SandboxPhase :: Stopped
@@ -7206,13 +7217,14 @@ mod tests {
72067217 use super :: {
72077218 PolicyGetView , ProvisioningStep , build_sandbox_resource_limits,
72087219 dockerfile_sources_supported_for_gateway, format_endpoint,
7209- format_provider_attachment_table, git_sync_files, inferred_provider_type,
7210- parse_cli_setting_value, parse_credential_expiry_cli_value, parse_credential_expiry_pairs,
7211- parse_credential_pairs, parse_driver_config_json, parse_secret_material_env_pairs,
7212- policy_revision_to_json, provider_profile_allows_empty_credentials,
7213- provisioning_timeout_message, ready_false_condition_message, refresh_status_header,
7214- refresh_status_row, resolve_from, sandbox_should_persist, sandbox_upload_plan,
7215- service_expose_status_error, service_url_for_gateway,
7220+ format_provider_attachment_table, git_sync_files, has_main_process_result,
7221+ inferred_provider_type, parse_cli_setting_value, parse_credential_expiry_cli_value,
7222+ parse_credential_expiry_pairs, parse_credential_pairs, parse_driver_config_json,
7223+ parse_secret_material_env_pairs, policy_revision_to_json,
7224+ provider_profile_allows_empty_credentials, provisioning_timeout_message,
7225+ ready_false_condition_message, refresh_status_header, refresh_status_row, resolve_from,
7226+ sandbox_should_persist, sandbox_upload_plan, service_expose_status_error,
7227+ service_url_for_gateway,
72167228 } ;
72177229 use crate :: TEST_ENV_LOCK ;
72187230 use crate :: commands:: common:: progress_step_from_metadata;
@@ -7751,6 +7763,48 @@ mod tests {
77517763 assert ! ( sandbox_should_persist( false , Some ( & spec) ) ) ;
77527764 }
77537765
7766+ #[ test]
7767+ fn infrastructure_error_with_observed_exit_is_not_a_main_process_result ( ) {
7768+ let mut sandbox = Sandbox {
7769+ status : Some ( SandboxStatus {
7770+ exit_code : Some ( 137 ) ,
7771+ conditions : vec ! [ SandboxCondition {
7772+ r#type: "Ready" . to_string( ) ,
7773+ status: "False" . to_string( ) ,
7774+ reason: "ComputeResourceMissing" . to_string( ) ,
7775+ message: "sandbox runtime disappeared" . to_string( ) ,
7776+ ..Default :: default ( )
7777+ } ] ,
7778+ ..Default :: default ( )
7779+ } ) ,
7780+ ..Default :: default ( )
7781+ } ;
7782+ sandbox. set_phase ( SandboxPhase :: Error as i32 ) ;
7783+
7784+ assert ! ( !has_main_process_result( & sandbox) ) ;
7785+ }
7786+
7787+ #[ test]
7788+ fn main_process_failed_condition_identifies_command_result ( ) {
7789+ let mut sandbox = Sandbox {
7790+ status : Some ( SandboxStatus {
7791+ exit_code : Some ( 7 ) ,
7792+ conditions : vec ! [ SandboxCondition {
7793+ r#type: "Ready" . to_string( ) ,
7794+ status: "False" . to_string( ) ,
7795+ reason: "MainProcessFailed" . to_string( ) ,
7796+ message: "canonical main process exited with status 7" . to_string( ) ,
7797+ ..Default :: default ( )
7798+ } ] ,
7799+ ..Default :: default ( )
7800+ } ) ,
7801+ ..Default :: default ( )
7802+ } ;
7803+ sandbox. set_phase ( SandboxPhase :: Error as i32 ) ;
7804+
7805+ assert ! ( has_main_process_result( & sandbox) ) ;
7806+ }
7807+
77547808 #[ test]
77557809 fn resolve_from_classifies_existing_dockerfile_path ( ) {
77567810 let temp = tempfile:: tempdir ( ) . expect ( "failed to create tempdir" ) ;
0 commit comments