From 91d78fdf17fb0cd651efb67c85bf3608a3025c26 Mon Sep 17 00:00:00 2001 From: Drew Newberry Date: Tue, 17 Mar 2026 18:28:16 -0700 Subject: [PATCH] fix(cli): suppress browser popup during auth via OPENSHELL_NO_BROWSER env var The browser_auth_flow opens the system browser even when stdin is piped/closed (e.g. in e2e tests), because read_line on a null stdin returns immediately and execution continues to open_browser(). Add OPENSHELL_NO_BROWSER=1 environment variable support that skips both the stdin prompt and the browser open call. The auth URL is still printed to stderr so users in headless environments can authenticate manually. Set the env var in the e2e test helpers to prevent browser popups when running tests locally. --- crates/openshell-cli/src/auth.rs | 32 +++++++++++++++++++++++--------- e2e/rust/tests/cf_auth_smoke.rs | 10 ++++++---- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/crates/openshell-cli/src/auth.rs b/crates/openshell-cli/src/auth.rs index 53d35ae1..6325ebf9 100644 --- a/crates/openshell-cli/src/auth.rs +++ b/crates/openshell-cli/src/auth.rs @@ -108,23 +108,37 @@ pub async fn browser_auth_flow(gateway_endpoint: &str) -> Result { gateway_endpoint.to_string(), )); + // Allow suppressing the browser popup via environment variable (useful for + // CI, e2e tests, and headless environments). + let no_browser = std::env::var("OPENSHELL_NO_BROWSER") + .map(|v| v == "1" || v.eq_ignore_ascii_case("true")) + .unwrap_or(false); + // Prompt the user before opening the browser. eprintln!(" Confirmation code: {code}"); eprintln!(" Verify this code matches your browser before clicking Connect."); eprintln!(); - eprint!("Press Enter to open the browser for authentication..."); - std::io::stderr().flush().ok(); - let mut _input = String::new(); - std::io::stdin().read_line(&mut _input).ok(); - - if let Err(e) = open_browser(&auth_url) { - debug!(error = %e, "failed to open browser"); - eprintln!("Could not open browser automatically."); + + if no_browser { + eprintln!("Browser opening suppressed (OPENSHELL_NO_BROWSER is set)."); eprintln!("Open this URL in your browser:"); eprintln!(" {auth_url}"); eprintln!(); } else { - eprintln!("Browser opened."); + eprint!("Press Enter to open the browser for authentication..."); + std::io::stderr().flush().ok(); + let mut _input = String::new(); + std::io::stdin().read_line(&mut _input).ok(); + + if let Err(e) = open_browser(&auth_url) { + debug!(error = %e, "failed to open browser"); + eprintln!("Could not open browser automatically."); + eprintln!("Open this URL in your browser:"); + eprintln!(" {auth_url}"); + eprintln!(); + } else { + eprintln!("Browser opened."); + } } // Wait for the callback or timeout. diff --git a/e2e/rust/tests/cf_auth_smoke.rs b/e2e/rust/tests/cf_auth_smoke.rs index 671c117a..43a2f96c 100644 --- a/e2e/rust/tests/cf_auth_smoke.rs +++ b/e2e/rust/tests/cf_auth_smoke.rs @@ -21,8 +21,9 @@ async fn run_isolated(args: &[&str]) -> (String, i32) { .env("XDG_CONFIG_HOME", tmpdir.path()) .env("HOME", tmpdir.path()) .env_remove("OPENSHELL_GATEWAY") - // `gateway add` may enter the browser auth flow, which prompts on stdin. - // Use a closed stdin so auth is skipped instead of hanging the test. + // Suppress browser popup during auth flow. + .env("OPENSHELL_NO_BROWSER", "1") + // Use a closed stdin so auth prompts don't hang the test. .stdin(Stdio::null()) .stdout(Stdio::piped()) .stderr(Stdio::piped()); @@ -43,8 +44,9 @@ async fn run_with_config(tmpdir: &std::path::Path, args: &[&str]) -> (String, i3 .env("XDG_CONFIG_HOME", tmpdir) .env("HOME", tmpdir) .env_remove("OPENSHELL_GATEWAY") - // `gateway add` may enter the browser auth flow, which prompts on stdin. - // Use a closed stdin so auth is skipped instead of hanging the test. + // Suppress browser popup during auth flow. + .env("OPENSHELL_NO_BROWSER", "1") + // Use a closed stdin so auth prompts don't hang the test. .stdin(Stdio::null()) .stdout(Stdio::piped()) .stderr(Stdio::piped());