diff --git a/crates/fbuild-cli/src/cli/deploy.rs b/crates/fbuild-cli/src/cli/deploy.rs index 63b82f46..25aaac6d 100644 --- a/crates/fbuild-cli/src/cli/deploy.rs +++ b/crates/fbuild-cli/src/cli/deploy.rs @@ -275,7 +275,7 @@ async fn maybe_recover_and_retry( let Some(request) = resp.usb_recovery.clone() else { return Ok(resp); }; - output::warn(format!( + output::diagnostic(format!( "deploy target {} is a known-unhealthy Windows devnode{}", request.instance_id, request @@ -312,20 +312,22 @@ async fn maybe_recover_and_retry( match outcome { RecoveryRunOutcome::ManualGuidance => { if policy == fbuild_core::usb::UsbRecoveryPolicy::Default { - output::warn( + output::diagnostic( "rerun with --admin to attempt a scoped one-shot Windows PnP recovery (UAC), or physically re-enter BOOTSEL (hold BOOT, tap RESET) and retry", ); } else { - output::warn("--no-admin: privileged recovery skipped by request"); + output::diagnostic("--no-admin: privileged recovery skipped by request"); } Ok(resp) } RecoveryRunOutcome::RefuseNonInteractive => { - output::warn("scoped PnP recovery needs an interactive Windows session; not elevating"); + output::diagnostic( + "scoped PnP recovery needs an interactive Windows session; not elevating", + ); Ok(resp) } RecoveryRunOutcome::Cancelled => { - output::warn("UAC prompt was cancelled; no recovery was attempted"); + output::diagnostic("UAC prompt was cancelled; no recovery was attempted"); Ok(resp) } RecoveryRunOutcome::Completed(result) => { @@ -345,7 +347,7 @@ async fn maybe_recover_and_retry( result.after, )); if !result.success { - output::warn(format!( + output::diagnostic(format!( "recovery helper reported {}; physical BOOTSEL replug remains the fallback", result .error_code @@ -381,7 +383,7 @@ async fn maybe_recover_and_retry( )); } None => { - output::warn( + output::diagnostic( "no healthy runtime CDC endpoint appeared after recovery; physical BOOTSEL replug remains the fallback", ); } diff --git a/crates/fbuild-daemon/src/handlers/operations/deploy.rs b/crates/fbuild-daemon/src/handlers/operations/deploy.rs index 74a5ec60..8f1820ee 100644 --- a/crates/fbuild-daemon/src/handlers/operations/deploy.rs +++ b/crates/fbuild-daemon/src/handlers/operations/deploy.rs @@ -966,7 +966,7 @@ pub async fn deploy( .await .unwrap_or_default(); let generation = super::deploy_port::rp_generation_for(board.as_ref()); - super::recovery_request::compose_rp2040_recovery_request( + let request = super::recovery_request::compose_rp2040_recovery_request( &devices, &problem_devices, &request_id, @@ -983,7 +983,27 @@ pub async fn deploy( generation, ) }, - ) + ); + // Recovery decisions must be observable after the fact: the request + // (or the reason none was composed) drives the CLI's --admin flow. + match &request { + Some(request) => tracing::info!( + instance = %request.instance_id, + problem_code = ?request.problem_code, + flash_completed = request.flash_completed, + "rp2040 recovery: composed typed exact-device recovery request" + ), + None => tracing::info!( + devices = devices.len(), + unhealthy_devices = devices + .iter() + .filter(|device| device.port_health.is_known_unhealthy()) + .count(), + problem_devices = problem_devices.len(), + "rp2040 recovery: no eligible exact-device recovery target in fresh scan" + ), + } + request } else { None };