From 7c06784271b840494ae598b45cdb69d248888cc6 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 4 Feb 2026 07:27:19 -0600 Subject: [PATCH] Fix deadlock on token refresh in `uv publish` when using pyx (#17832) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently `uv publish` will always deadlock on publish to pyx if the token is expired. See https://github.com/astral-sh/uv/pull/17832#discussion_r2760607829 ``` ❯ uv publish ... -v DEBUG uv 0.9.28 (0e1351e40 2026-01-29) Checking 19743 files against https://api.pyx.dev/v1/upload/internal/... DEBUG Using request timeout of 900s DEBUG Using request timeout of 30s DEBUG Using request timeout of 900s DEBUG Refreshing token due to token expired (`2026-02-03T19:38:28Z`) DEBUG Acquired exclusive lock for `pyx refresh` DEBUG Token on disk still needs refresh due to token expired (`2026-02-03T19:38:28Z`) DEBUG Refreshing token due to token expired (`2026-02-03T19:38:28Z`) INFO Waiting to acquire exclusive lock for `pyx refresh` at `/Users/zb/.local/share/pyx/credentials/3859a629b26fda96/tokens.lock` ``` --- crates/uv/src/commands/publish.rs | 37 +++++++++++-------------------- 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/crates/uv/src/commands/publish.rs b/crates/uv/src/commands/publish.rs index 576ba226e..fd456a3dc 100644 --- a/crates/uv/src/commands/publish.rs +++ b/crates/uv/src/commands/publish.rs @@ -6,7 +6,7 @@ use console::Term; use owo_colors::{AnsiColors, OwoColorize}; use tokio::sync::Semaphore; use tracing::{debug, info, trace}; -use uv_auth::{Credentials, DEFAULT_TOLERANCE_SECS, PyxTokenStore}; +use uv_auth::{Credentials, PyxTokenStore}; use uv_cache::Cache; use uv_client::{ AuthIntegration, BaseClient, BaseClientBuilder, RedirectPolicy, RegistryClientBuilder, @@ -171,7 +171,6 @@ pub(crate) async fn publish( keyring_provider, &token_store, &oidc_client, - &upload_client, check_url.as_ref(), Prompt::Enabled, printer, @@ -435,7 +434,6 @@ async fn gather_credentials( keyring_provider: KeyringProviderType, token_store: &PyxTokenStore, oidc_client: &BaseClient, - base_client: &BaseClient, check_url: Option<&IndexUrl>, prompt: Prompt, printer: Printer, @@ -461,22 +459,6 @@ async fn gather_credentials( .expect("Failed to clear publish URL username"); } - // If the user is publishing to pyx, load the credentials from the store. - if username.is_none() && password.is_none() { - if token_store.is_known_url(&publish_url) { - if let Some(token) = token_store - .access_token( - base_client.for_host(token_store.api()).raw_client(), - DEFAULT_TOLERANCE_SECS, - ) - .await? - { - debug!("Using authentication token from the store"); - return Ok((publish_url, Credentials::from(token))); - } - } - } - // If applicable, attempt obtaining a token for trusted publishing. let trusted_publishing_token = check_trusted_publishing( username.as_deref(), @@ -494,9 +476,14 @@ async fn gather_credentials( (Some("__token__".to_string()), Some(password.to_string())) } else { if username.is_none() && password.is_none() { - match prompt { - Prompt::Enabled => prompt_username_and_password()?, - Prompt::Disabled => (None, None), + // Skip prompting for pyx URLs; the auth middleware will handle authentication. + if token_store.is_known_url(&publish_url) { + (None, None) + } else { + match prompt { + Prompt::Enabled => prompt_username_and_password()?, + Prompt::Disabled => (None, None), + } } } else { (username, password) @@ -511,7 +498,10 @@ async fn gather_credentials( ); } - if username.is_none() && password.is_none() && keyring_provider == KeyringProviderType::Disabled + if username.is_none() + && password.is_none() + && keyring_provider == KeyringProviderType::Disabled + && !token_store.is_known_url(&publish_url) { if let TrustedPublishResult::Ignored(err) = trusted_publishing_token { // The user has configured something incorrectly: @@ -608,7 +598,6 @@ mod tests { KeyringProviderType::Disabled, &token_store, &client, - &client, None, Prompt::Disabled, Printer::Quiet,