Limit `uv auth login pyx.dev` retries to 60s (#16498)

## Summary

Without this, a user who does `uv auth login ...` will retry against the
service's status endpoint forever. This probably isn't what they
intended (they probably walked away from their machine), so we end their
login initiation session after 60 retries. Since we do a retry every
second, this gives them no less than a minute to complete a login (which
should be more than enough).

## Test Plan

We don't have browser-negotiated login tests at the moment in CI, but
I've tested this locally:

```console
% ./target/debug/uv auth login pyx.dev
Logging in with https://api.pyx.dev/auth/cli/login/REDACTED
error: Login session timed out
```

(That took well over a minute, so 60s is a lower bound assuming a very
optimal network roundtrip on each poll.)

---------

Signed-off-by: William Woodruff <william@astral.sh>
This commit is contained in:
William Woodruff 2025-10-29 11:39:23 -04:00 committed by GitHub
parent 2d54f329e5
commit c6d0b412a0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 11 additions and 0 deletions

View File

@ -19,6 +19,9 @@ use crate::commands::ExitStatus;
use crate::printer::Printer; use crate::printer::Printer;
use crate::settings::NetworkSettings; use crate::settings::NetworkSettings;
// We retry no more than this many times when polling for login status.
const STATUS_RETRY_LIMIT: u32 = 60;
/// Login to a service. /// Login to a service.
pub(crate) async fn login( pub(crate) async fn login(
service: Service, service: Service,
@ -215,6 +218,7 @@ pub(crate) async fn pyx_login_with_browser(
url url
}; };
let mut retry = 0;
let credentials = loop { let credentials = loop {
let response = client let response = client
.for_host(store.api()) .for_host(store.api())
@ -225,6 +229,7 @@ pub(crate) async fn pyx_login_with_browser(
// Retry on 404. // Retry on 404.
reqwest::StatusCode::NOT_FOUND => { reqwest::StatusCode::NOT_FOUND => {
tokio::time::sleep(std::time::Duration::from_secs(1)).await; tokio::time::sleep(std::time::Duration::from_secs(1)).await;
retry += 1;
} }
// Parse the credentials on success. // Parse the credentials on success.
_ if response.status().is_success() => { _ if response.status().is_success() => {
@ -236,6 +241,12 @@ pub(crate) async fn pyx_login_with_browser(
break Err(anyhow::anyhow!("Failed to login with code `{status}`")); break Err(anyhow::anyhow!("Failed to login with code `{status}`"));
} }
} }
if retry >= STATUS_RETRY_LIMIT {
break Err(anyhow::anyhow!(
"Login session timed out after {STATUS_RETRY_LIMIT} seconds"
));
}
}?; }?;
store.write(&credentials).await?; store.write(&credentials).await?;