jdx/mise · error · eyre::Report
blob chunk upload failed: {}{} {}
Error message
blob chunk upload failed: {}{}
{} What it means
For blobs larger than the upload chunk size, mise PATCHes bytes in chunks and expects 202 Accepted per the OCI dist-spec (201 also accepted because AWS ECR answers Created). A chunk PATCH returning anything else — with auth hint and body — fails the whole upload. Transient 5xx/408/429 were already retried before this line.
Source
Thrown at src/oci/registry.rs:1364
offset,
len,
pr,
&err_slot,
)
// Content-Range is inclusive on both ends.
.header("Content-Range", format!("{}-{}", offset, offset + len - 1)))
})
.await
.wrap_err("PATCH blob chunk")?;
check_upload_err(&err_slot, path)?;
let status = resp.status();
// Per the OCI dist-spec a chunk PATCH returns 202 Accepted, but
// AWS ECR answers with 201 Created. Accept both, as the
// finalizing PUT below already does.
if status != StatusCode::ACCEPTED && status != StatusCode::CREATED {
resp.error_for_status_ref()?;
let body = resp.text().await.unwrap_or_default();
bail!(
"blob chunk upload failed: {}{}\n{}",
status.as_u16(),
push_auth_hint(status, had_credential),
body.trim(),
);
}
location = self.resolve_location(&resp).unwrap_or(location);
offset += len;
}
// Finalize with ?digest=…
let mut put_url = location;
put_url.query_pairs_mut().append_pair("digest", digest);
let resp = self
.session
.send(|auth| {
let mut rb = HTTP
.reqwest()?
.put(put_url.as_str())View on GitHub (pinned to 9dcfcaa0dc)
Solutions
- Simply re-run the push — a fresh upload session usually completes if the first died of session/token expiry
- Shrink the pushed layers (smaller base, fewer bundled tools) so the upload finishes within the registry's session lifetime
- For 401 mid-upload: use a longer-lived token or re-login immediately before pushing
- Check the body for registry-specific codes (BLOB_UPLOAD_INVALID, DIGEST_INVALID) pointing at range/digest mismatches
Defensive patterns
Strategy: retry
Validate before calling
# Reduce exposure to chunked-upload failures before pushing:
# - keep layers small (smaller bases, fewer bundled toolchains)
# - verify egress allows large PATCH requests through proxies:
curl -sS -o /dev/null -w '%{http_code}\n' -X PATCH https://registry.example.com/v2/ # expect non-5xx routing response Try / catch
// Chunk failures are usually session/token expiry — one bounded retry helps:
for attempt in 1..=2 {
match run_mise_oci_push().await {
Ok(_) => break,
Err(e) if e.to_string().contains("blob chunk upload failed") && attempt < 2 => {
relogin_if_needed().await; // refresh short-lived tokens
tokio::time::sleep(Duration::from_secs(10)).await;
}
Err(e) => return Err(e),
}
} Prevention
- Re-login immediately before pushing when using short-lived registry tokens
- Shrink layers so uploads finish within the registry's session TTL
- Check the embedded body for BLOB_UPLOAD_INVALID/DIGEST_INVALID to distinguish range bugs from expiry
When it happens
Trigger: The upload session Location URL expiring mid-transfer on slow links (registries kill idle/long sessions); an auth token expiring between chunks (401); Content-Range mismatch after a retry re-sent a chunk; intermediate proxies rejecting PATCH.
Common situations: Pushing large toolchain layers (100s of MB) over slow or proxy-intercepted CI networks; long uploads against registries with short session TTLs (some ECR/Zot configurations).
Related errors
- blob upload failed: {}{} {}
- fetching {manifest_url} failed: {}
- starting blob upload failed: {} {}{}
- fetching {url} failed: {}{hint} {}
- fetching blob {url} failed: {}
AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17).
Data as JSON: /api/errors/dca6eebfed7ed3e6.
Report an issue: GitHub.