jdx/mise · error · eyre::Report
blob upload failed: {}{} {}
Error message
blob upload failed: {}{}
{} What it means
Final step of the chunked blob upload: a PUT with ?digest=<sha256> to finalize the session. A non-success response fails here with status, auth hint, and body. Digest mismatches (corrupted transfer), expired sessions, or lost permissions at finalize time are the usual statuses.
Source
Thrown at src/oci/registry.rs:1395
let resp = self
.session
.send(|auth| {
let mut rb = HTTP
.reqwest()?
.put(put_url.as_str())
.header("Content-Length", "0");
if let Some(a) = auth {
rb = rb.header("Authorization", a);
}
Ok(rb)
})
.await
.wrap_err("PUT blob upload (finalize)")?;
let status = resp.status();
if !status.is_success() {
resp.error_for_status_ref()?;
let body = resp.text().await.unwrap_or_default();
bail!(
"blob upload failed: {}{}\n{}",
status.as_u16(),
push_auth_hint(status, had_credential),
body.trim(),
);
}
} else {
// Monolithic PUT with ?digest=…
let mut put_url = location;
put_url.query_pairs_mut().append_pair("digest", digest);
let err_slot: UploadErrSlot = Default::default();
let resp = self
.session
.send(|auth| {
Ok(build_upload_request(
HTTP.reqwest()?.put(put_url.as_str()),
auth,
path,View on GitHub (pinned to 9dcfcaa0dc)
Solutions
- Re-run the push — a fresh session and re-upload resolves expiry/transient corruption
- If DIGEST_INVALID recurs, verify build-machine disk health and reduce parallel memory pressure (corrupt temp files produce wrong digests deterministically)
- Reduce layer size or split builds so finalize happens sooner after the last chunk
- Check the appended body text for the registry's exact error code
Defensive patterns
Strategy: retry
Validate before calling
# Detect corrupt local blobs before push by re-hashing the largest layers:
sha256sum <image_dir>/blobs/sha256/* | awk '{print $1}' \
| while read h; do [ -f "<image_dir>/blobs/sha256/$h" ] || echo "missing blob $h"; done
# A blob whose filename digest mismatches its content will deterministically
# fail the finalize PUT — regenerate the layout instead of retrying forever. Try / catch
// Distinguish deterministic digest failures from transient finalize expiry:
let msg = String::from_utf8_lossy(&out.stderr);
if msg.contains("blob upload failed") {
if msg.to_uppercase().contains("DIGEST_INVALID") {
// corruption — re-run `mise oci build`, do NOT retry the push
} else {
// session expiry / transient — retry the push once with a fresh session
}
} Prevention
- Keep build and push on the same machine/step to avoid artifact corruption in transit
- Watch CI runner disk health — corrupted temp files produce wrong digests deterministically
- Retry finalize failures once; escalate to rebuild only when DIGEST_INVALID appears
When it happens
Trigger: The uploaded bytes do not hash to the expected digest (400 DIGEST_INVALID — corruption in temp files or on the wire); the session Location expired before the finalize PUT; auth dropped between the last chunk and finalize.
Common situations: Flaky networks corrupting large uploads; CI runners with failing scratch disks corrupting the layer blob between build and push; registries with aggressive upload-session timeouts on big layers.
Related errors
- blob chunk 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/5ac13828f2bb0fd0.
Report an issue: GitHub.