From 0876977133f966840a15d76ed9fb001e727c52a8 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sun, 9 Aug 2026 22:22:00 +0200 Subject: [PATCH] Report a missing browser launcher instead of faking success MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `open_in_browser` gated its xdg-open path on `target_os = "linux"`, which is false on Android — that is its own target_os. Android therefore took the fallback arm, which discarded the URL and returned `Ok(())`. Login Flow v2 cannot complete without a browser: the user approves the sign-in there and `auth::poll` waits for that approval. Claiming success meant the UI showed "Approve the sign-in in your browser" with no browser open, the poll waited for an approval that could never arrive, and the worker eventually dropped its channel — surfacing as "sign-in failed unexpectedly", which pointed at the network rather than at the real cause. The server had in fact been contacted successfully. Gate on `unix && !android && !macos` so the arm matches what xdg-open actually implies, and return Unsupported from the fallback. The caller treats it as terminal rather than swallowing it with `let _ =`. Android gets a real Intent-based launcher in the next commit; until then the failure is at least honest about what happened. Co-Authored-By: Claude Opus 5 --- ui/dr-ui/src/launch_ui.rs | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/ui/dr-ui/src/launch_ui.rs b/ui/dr-ui/src/launch_ui.rs index 6ee2877..8201ec7 100644 --- a/ui/dr-ui/src/launch_ui.rs +++ b/ui/dr-ui/src/launch_ui.rs @@ -290,7 +290,16 @@ fn spawn_login(weak: slint::Weak, ctl: Rc, server: }; // Open the system browser, never an embedded webview (FR-NC-1). - let _ = open_in_browser(&flow.login_url); + // + // Failing here is terminal: the poll below waits for an approval + // that only the browser can give, so carrying on would hang until + // the flow expired and then report nothing useful. + if let Err(e) = open_in_browser(&flow.login_url) { + let _ = tx.send(LoginMessage::Failed(format!( + "could not open a browser to approve the sign-in: {e}" + ))); + return; + } let _ = tx.send(LoginMessage::AwaitingApproval(flow.login_url.clone())); match auth::poll(&client, &flow).await { @@ -526,8 +535,16 @@ async fn fetch_user_id( } /// Open a URL in the system browser. +/// +/// Login Flow v2 cannot complete without this: the user approves the sign-in +/// in a browser and the poll below waits for that approval. So a platform with +/// no way to open one must say so rather than return `Ok(())` — reporting +/// success here strands the flow on "Approve the sign-in in your browser" with +/// no browser and no explanation. fn open_in_browser(url: &str) -> std::io::Result<()> { - #[cfg(target_os = "linux")] + // Not `target_os = "linux"`: Android is its own target_os, and reached this + // arm's `Ok(())` fallback, so the browser silently never opened. + #[cfg(all(unix, not(target_os = "android"), not(target_os = "macos")))] { std::process::Command::new("xdg-open") .arg(url) @@ -536,9 +553,12 @@ fn open_in_browser(url: &str) -> std::io::Result<()> { .spawn() .map(|_| ()) } - #[cfg(not(target_os = "linux"))] + #[cfg(not(all(unix, not(target_os = "android"), not(target_os = "macos"))))] { let _ = url; - Ok(()) + Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "no browser launcher on this platform", + )) } }