From 23406d9be8c941a0f3d9dc24cb9d648d28b1a5ab Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Fri, 16 Oct 2026 22:51:41 -0700 Subject: [PATCH] feat(cli): let the browser launcher end lite's sign-in wait when it cannot open --- litellm/proxy/client/cli/commands/pkce_login.py | 12 +++++++++++- tests/unit/proxy/client/cli/test_pkce_login.py | 16 ++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/client/cli/commands/pkce_login.py b/litellm/proxy/client/cli/commands/pkce_login.py index c9dd542eb36..21823b26530 100644 --- a/litellm/proxy/client/cli/commands/pkce_login.py +++ b/litellm/proxy/client/cli/commands/pkce_login.py @@ -128,6 +128,7 @@ class LoopbackServer(HTTPServer): self.expected_state: Final = expected_state self.connection_timeout_seconds: Final = connection_timeout_seconds self.outcome: CallbackOutcome | None = None + self.launch_failure: PkceFailure | None = None self.timeout = 1 @property @@ -139,11 +140,18 @@ class LoopbackServer(HTTPServer): accepted[0].settimeout(self.connection_timeout_seconds) return accepted + def launch_browser(self, open_browser: Callable[[str], object], url: str) -> None: + opened: Final = open_browser(url) + if isinstance(opened, PkceFailure): + self.launch_failure = opened + def wait( self, timeout_seconds: float, clock: Callable[[], float] = time.monotonic ) -> CallbackOutcome | PkceFailure: deadline: Final = clock() + timeout_seconds while self.outcome is None: + if self.launch_failure is not None: + return self.launch_failure if clock() >= deadline: return PkceFailure("timed out waiting for the browser sign-in to finish") self.handle_request() @@ -454,7 +462,9 @@ def run_pkce_login( url: Final = authorize_url(contract, client_id, server.redirect_uri, state, challenge) echo(f"Opening browser to: {url}") echo("Approve the sign-in in your browser. Waiting...") - threading.Thread(target=open_browser, args=(url,), name="lite-login-browser", daemon=True).start() + threading.Thread( + target=server.launch_browser, args=(open_browser, url), name="lite-login-browser", daemon=True + ).start() outcome: Final = server.wait(timeout_seconds) match outcome: case PkceFailure(): diff --git a/tests/unit/proxy/client/cli/test_pkce_login.py b/tests/unit/proxy/client/cli/test_pkce_login.py index f58bd0ff412..270beb36b3c 100644 --- a/tests/unit/proxy/client/cli/test_pkce_login.py +++ b/tests/unit/proxy/client/cli/test_pkce_login.py @@ -549,6 +549,22 @@ def test_run_pkce_login_stops_before_the_browser_when_registration_fails(): assert opened == [] +def test_run_pkce_login_stops_waiting_when_the_browser_launcher_reports_it_did_not_open(): + http = _FakeHttp( + { + ("GET", DISCOVERY_URL): _FakeResponse(200, CONTRACT_DOC), + ("POST", f"{BASE}/register"): _FakeResponse(201, {"client_id": "llm_dcrc_abc"}), + } + ) + launch_failure = PkceFailure("the web browser did not open") + + result = run_pkce_login( + BASE, http, open_browser=lambda _url: launch_failure, echo=lambda _: None, timeout_seconds=30 + ) + + assert result == launch_failure + + def test_pkce_token_record_keeps_every_refresh_input_next_to_the_key(): credential = PkceCredential( access_token="sk-cli-new",