Fix deadlock on token refresh in uv publish when using pyx (#17832)

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`
```
This commit is contained in:
Zanie Blue
2026-02-04 07:27:19 -06:00
committed by GitHub
parent 30e2c2b57d
commit 7c06784271
+13 -24
View File
@@ -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,