From 6c99e83b44bcb1b58f10f3f95eca93c27d55f81c Mon Sep 17 00:00:00 2001 From: divesh Date: Fri, 6 Nov 2026 10:55:52 -0800 Subject: [PATCH] fix(cli): report scheduling failures in provisioning timeouts Fixes #5230 Signed-off-by: divesh --- crates/openshell-cli/src/commands/common.rs | 14 ++++- crates/openshell-cli/src/run.rs | 67 +++++++++++++++++++++ 2 files changed, 78 insertions(+), 3 deletions(-) diff --git a/crates/openshell-cli/src/commands/common.rs b/crates/openshell-cli/src/commands/common.rs index 4afeeb569c..23670c0b19 100644 --- a/crates/openshell-cli/src/commands/common.rs +++ b/crates/openshell-cli/src/commands/common.rs @@ -576,9 +576,17 @@ pub fn print_sandbox_header( pub fn ready_false_condition_message( status: Option<&openshell_core::proto::SandboxStatus>, ) -> Option { - let condition = status?.conditions.iter().find(|condition| { - condition.r#type == "Ready" && condition.status.eq_ignore_ascii_case("false") - })?; + let conditions = &status?.conditions; + let condition = conditions + .iter() + .find(|condition| { + condition.r#type == "PodScheduled" && condition.status.eq_ignore_ascii_case("false") + }) + .or_else(|| { + conditions.iter().find(|condition| { + condition.r#type == "Ready" && condition.status.eq_ignore_ascii_case("false") + }) + })?; if condition.message.is_empty() { if condition.reason.is_empty() { diff --git a/crates/openshell-cli/src/run.rs b/crates/openshell-cli/src/run.rs index eb224685ed..4c997f18e0 100644 --- a/crates/openshell-cli/src/run.rs +++ b/crates/openshell-cli/src/run.rs @@ -8241,6 +8241,73 @@ mod tests { ); } + #[test] + fn provisioning_timeout_prefers_failed_scheduling_over_ready_placeholder() { + let ready = SandboxCondition { + r#type: "Ready".into(), + status: "False".into(), + reason: "ConfigurationPending".into(), + message: "Waiting for effective configuration validation before workload activation" + .into(), + transition_time: None, + }; + let scheduled = SandboxCondition { + r#type: "PodScheduled".into(), + status: "false".into(), + reason: "Unschedulable".into(), + message: "0/5 nodes are available: 1 Too many pods".into(), + transition_time: None, + }; + for conditions in [ + vec![ready.clone(), scheduled.clone()], + vec![scheduled.clone(), ready], + vec![scheduled], + ] { + let status = SandboxStatus { + conditions, + ..Default::default() + }; + let detail = ready_false_condition_message(Some(&status)); + assert_eq!( + detail.as_deref(), + Some("Unschedulable: 0/5 nodes are available: 1 Too many pods") + ); + assert_eq!( + provisioning_timeout_message(300, None, detail.as_deref()), + "sandbox provisioning timed out after 300s. Last reported status: Unschedulable: 0/5 nodes are available: 1 Too many pods" + ); + } + } + + #[test] + fn ready_false_condition_message_falls_back_when_scheduling_has_not_failed() { + for scheduled_status in ["True", "Unknown", ""] { + let status = SandboxStatus { + conditions: vec![ + SandboxCondition { + r#type: "PodScheduled".into(), + status: scheduled_status.into(), + reason: "SchedulingStatus".into(), + message: "Scheduling observation".into(), + transition_time: None, + }, + SandboxCondition { + r#type: "Ready".into(), + status: "False".into(), + reason: "ConfigurationPending".into(), + message: "Waiting for configuration".into(), + transition_time: None, + }, + ], + ..Default::default() + }; + assert_eq!( + ready_false_condition_message(Some(&status)).as_deref(), + Some("ConfigurationPending: Waiting for configuration") + ); + } + } + #[test] fn ready_false_condition_message_ignores_non_ready_conditions() { let status = SandboxStatus {