Skip to content

Commit 0bfd820

Browse files
authored
Add more context to publish-failed error message (#15879)
## What does this PR try to resolve? This PR fixes issue #15754 where workspace publish failures provided non-actionable error messages. Previously, when publishing a workspace failed (due to rate limiting or other errors), users only saw generic errors like `[ERROR] failed to publish to registry` without knowing which package failed or what packages remained to be published. ## How to test and review this PR? **Testing**: Run `cargo test workspace_publish_rate_limit_error` to see the improved behavior, then `cargo test publish` to ensure all tests pass. **Review**: - **Commit 1**: Adds test demonstrating current problematic behavior - **Commit 2**: Implements fix and updates test to expect improved output The fix transforms error messages from generic `failed to publish to registry` to actionable `failed to publish 'package_a' v0.1.0; the following crates have not been published yet: package_b v0.1.0, package_c v0.1.0`. This improvement applies consistently across all publish error scenarios, giving users clear information about what went wrong and what remains to be done.
2 parents 623d536 + d2a6dcb commit 0bfd820

File tree

2 files changed

+139
-10
lines changed

2 files changed

+139
-10
lines changed

src/cargo/ops/registry/publish.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,8 @@ pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> {
210210
// `b`, and we uploaded `a` and `b` but only confirmed `a`, then on
211211
// the following pass through the outer loop nothing will be ready for
212212
// upload.
213-
for pkg_id in plan.take_ready() {
213+
let mut ready = plan.take_ready();
214+
while let Some(pkg_id) = ready.pop_first() {
214215
let (pkg, (_features, tarball)) = &pkg_dep_graph.packages[&pkg_id];
215216
opts.gctx.shell().status("Uploading", pkg.package_id())?;
216217

@@ -236,6 +237,19 @@ pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> {
236237
)?));
237238
}
238239

240+
let workspace_context = || {
241+
let mut remaining = ready.clone();
242+
remaining.extend(plan.iter());
243+
if !remaining.is_empty() {
244+
format!(
245+
"\n\nnote: the following crates have not been published yet:\n {}",
246+
remaining.into_iter().join("\n ")
247+
)
248+
} else {
249+
String::new()
250+
}
251+
};
252+
239253
transmit(
240254
opts.gctx,
241255
ws,
@@ -244,6 +258,7 @@ pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> {
244258
&mut registry,
245259
source_ids.original,
246260
opts.dry_run,
261+
workspace_context,
247262
)?;
248263
to_confirm.insert(pkg_id);
249264

@@ -632,6 +647,7 @@ fn transmit(
632647
registry: &mut Registry,
633648
registry_id: SourceId,
634649
dry_run: bool,
650+
workspace_context: impl Fn() -> String,
635651
) -> CargoResult<()> {
636652
let new_crate = prepare_transmit(gctx, ws, pkg, registry_id)?;
637653

@@ -641,9 +657,15 @@ fn transmit(
641657
return Ok(());
642658
}
643659

644-
let warnings = registry
645-
.publish(&new_crate, tarball)
646-
.with_context(|| format!("failed to publish to registry at {}", registry.host()))?;
660+
let warnings = registry.publish(&new_crate, tarball).with_context(|| {
661+
format!(
662+
"failed to publish {} v{} to registry at {}{}",
663+
pkg.name(),
664+
pkg.version(),
665+
registry.host(),
666+
workspace_context()
667+
)
668+
})?;
647669

648670
if !warnings.invalid_categories.is_empty() {
649671
let msg = format!(

tests/testsuite/publish.rs

Lines changed: 113 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2260,7 +2260,7 @@ fn api_error_json() {
22602260
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
22612261
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
22622262
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
2263-
[ERROR] failed to publish to registry at http://127.0.0.1:[..]/
2263+
[ERROR] failed to publish foo v0.0.1 to registry at http://127.0.0.1:[..]/
22642264
22652265
Caused by:
22662266
the remote server responded with an error (status 403 Forbidden): you must be logged in
@@ -2308,7 +2308,7 @@ fn api_error_200() {
23082308
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
23092309
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
23102310
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
2311-
[ERROR] failed to publish to registry at http://127.0.0.1:[..]/
2311+
[ERROR] failed to publish foo v0.0.1 to registry at http://127.0.0.1:[..]/
23122312
23132313
Caused by:
23142314
the remote server responded with an [ERROR] max upload size is 123
@@ -2356,7 +2356,7 @@ fn api_error_code() {
23562356
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
23572357
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
23582358
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
2359-
[ERROR] failed to publish to registry at http://127.0.0.1:[..]/
2359+
[ERROR] failed to publish foo v0.0.1 to registry at http://127.0.0.1:[..]/
23602360
23612361
Caused by:
23622362
failed to get a 200 OK response, got 400
@@ -2413,7 +2413,7 @@ fn api_curl_error() {
24132413
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
24142414
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
24152415
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
2416-
[ERROR] failed to publish to registry at http://127.0.0.1:[..]/
2416+
[ERROR] failed to publish foo v0.0.1 to registry at http://127.0.0.1:[..]/
24172417
24182418
Caused by:
24192419
[52] Server returned nothing (no headers, no data) (Empty reply from server)
@@ -2461,7 +2461,7 @@ fn api_other_error() {
24612461
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
24622462
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
24632463
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
2464-
[ERROR] failed to publish to registry at http://127.0.0.1:[..]/
2464+
[ERROR] failed to publish foo v0.0.1 to registry at http://127.0.0.1:[..]/
24652465
24662466
Caused by:
24672467
invalid response body from server
@@ -3608,7 +3608,7 @@ fn invalid_token() {
36083608
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
36093609
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
36103610
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
3611-
[ERROR] failed to publish to registry at http://127.0.0.1:[..]/
3611+
[ERROR] failed to publish foo v0.0.1 to registry at http://127.0.0.1:[..]/
36123612
36133613
Caused by:
36143614
token contains invalid characters.
@@ -4402,3 +4402,110 @@ See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for
44024402
"#]])
44034403
.run();
44044404
}
4405+
4406+
#[cargo_test]
4407+
fn workspace_publish_rate_limit_error() {
4408+
let registry = registry::RegistryBuilder::new()
4409+
.http_api()
4410+
.http_index()
4411+
.add_responder("/api/v1/crates/new", |_req, _| {
4412+
// For simplicity, let's just return rate limit error for all requests
4413+
// This simulates hitting rate limit during workspace publish
4414+
Response {
4415+
code: 429,
4416+
headers: vec!["Retry-After: 3600".to_string()],
4417+
body: format!(
4418+
"You have published too many new crates in a short period of time. Please try again after Fri, 18 Jul 2025 20:00:34 GMT or email [email protected] to have your limit increased."
4419+
).into_bytes(),
4420+
}
4421+
})
4422+
.build();
4423+
4424+
let p = project()
4425+
.file(
4426+
"Cargo.toml",
4427+
r#"
4428+
[workspace]
4429+
members = ["package_a", "package_b", "package_c"]
4430+
"#,
4431+
)
4432+
.file("src/lib.rs", "")
4433+
.file(
4434+
"package_a/Cargo.toml",
4435+
r#"
4436+
[package]
4437+
name = "package_a"
4438+
version = "0.1.0"
4439+
edition = "2015"
4440+
license = "MIT"
4441+
description = "package a"
4442+
repository = "https://github.com/test/package_a"
4443+
"#,
4444+
)
4445+
.file("package_a/src/lib.rs", "")
4446+
.file(
4447+
"package_b/Cargo.toml",
4448+
r#"
4449+
[package]
4450+
name = "package_b"
4451+
version = "0.1.0"
4452+
edition = "2015"
4453+
license = "MIT"
4454+
description = "package b"
4455+
repository = "https://github.com/test/package_b"
4456+
"#,
4457+
)
4458+
.file("package_b/src/lib.rs", "")
4459+
.file(
4460+
"package_c/Cargo.toml",
4461+
r#"
4462+
[package]
4463+
name = "package_c"
4464+
version = "0.1.0"
4465+
edition = "2015"
4466+
license = "MIT"
4467+
description = "package c"
4468+
repository = "https://github.com/test/package_c"
4469+
4470+
[dependencies]
4471+
package_a = { version = "0.1.0", path = "../package_a" }
4472+
"#,
4473+
)
4474+
.file("package_c/src/lib.rs", "")
4475+
.build();
4476+
4477+
// This demonstrates the improved error message after the fix
4478+
// The user now knows which package failed and what packages remain to be published
4479+
p.cargo("publish --workspace --no-verify")
4480+
.replace_crates_io(registry.index_url())
4481+
.with_status(101)
4482+
.with_stderr_data(str![[r#"
4483+
[UPDATING] crates.io index
4484+
[PACKAGING] package_a v0.1.0 ([ROOT]/foo/package_a)
4485+
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
4486+
[PACKAGING] package_b v0.1.0 ([ROOT]/foo/package_b)
4487+
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
4488+
[PACKAGING] package_c v0.1.0 ([ROOT]/foo/package_c)
4489+
[UPDATING] crates.io index
4490+
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
4491+
[UPLOADING] package_a v0.1.0 ([ROOT]/foo/package_a)
4492+
[ERROR] failed to publish package_a v0.1.0 to registry at http://127.0.0.1:[..]/
4493+
4494+
[NOTE] the following crates have not been published yet:
4495+
package_b v0.1.0 ([ROOT]/foo/package_b)
4496+
package_c v0.1.0 ([ROOT]/foo/package_c)
4497+
4498+
Caused by:
4499+
failed to get a 200 OK response, got 429
4500+
headers:
4501+
HTTP/1.1 429
4502+
Content-Length: 172
4503+
Connection: close
4504+
Retry-After: 3600
4505+
4506+
body:
4507+
You have published too many new crates in a short period of time. Please try again after Fri, 18 Jul 2025 20:00:34 GMT or email [email protected] to have your limit increased.
4508+
4509+
"#]])
4510+
.run();
4511+
}

0 commit comments

Comments
 (0)