{"record":{"id":"dca6eebfed7ed3e6","repo":"jdx/mise","slug":"blob-chunk-upload-failed","errorCode":null,"errorMessage":"blob chunk upload failed: {}{}\n{}","messagePattern":"blob chunk upload failed: (.+?)(.+?)\n(.+?)","errorType":"http","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"src/oci/registry.rs","lineNumber":1364,"sourceCode":"                            offset,\n                            len,\n                            pr,\n                            &err_slot,\n                        )\n                        // Content-Range is inclusive on both ends.\n                        .header(\"Content-Range\", format!(\"{}-{}\", offset, offset + len - 1)))\n                    })\n                    .await\n                    .wrap_err(\"PATCH blob chunk\")?;\n                check_upload_err(&err_slot, path)?;\n                let status = resp.status();\n                // Per the OCI dist-spec a chunk PATCH returns 202 Accepted, but\n                // AWS ECR answers with 201 Created. Accept both, as the\n                // finalizing PUT below already does.\n                if status != StatusCode::ACCEPTED && status != StatusCode::CREATED {\n                    resp.error_for_status_ref()?;\n                    let body = resp.text().await.unwrap_or_default();\n                    bail!(\n                        \"blob chunk upload failed: {}{}\\n{}\",\n                        status.as_u16(),\n                        push_auth_hint(status, had_credential),\n                        body.trim(),\n                    );\n                }\n                location = self.resolve_location(&resp).unwrap_or(location);\n                offset += len;\n            }\n            // Finalize with ?digest=…\n            let mut put_url = location;\n            put_url.query_pairs_mut().append_pair(\"digest\", digest);\n            let resp = self\n                .session\n                .send(|auth| {\n                    let mut rb = HTTP\n                        .reqwest()?\n                        .put(put_url.as_str())","sourceCodeStart":1346,"sourceCodeEnd":1382,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/oci/registry.rs#L1346-L1382","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"# Reduce exposure to chunked-upload failures before pushing:\n# - keep layers small (smaller bases, fewer bundled toolchains)\n# - verify egress allows large PATCH requests through proxies:\ncurl -sS -o /dev/null -w '%{http_code}\\n' -X PATCH https://registry.example.com/v2/ # expect non-5xx routing response","typeGuard":null,"tryCatchPattern":"// Chunk failures are usually session/token expiry — one bounded retry helps:\nfor attempt in 1..=2 {\n    match run_mise_oci_push().await {\n        Ok(_) => break,\n        Err(e) if e.to_string().contains(\"blob chunk upload failed\") && attempt < 2 => {\n            relogin_if_needed().await; // refresh short-lived tokens\n            tokio::time::sleep(Duration::from_secs(10)).await;\n        }\n        Err(e) => return Err(e),\n    }\n}","preventionTips":["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"],"tags":["mise","oci","registry","push","upload","chunked-upload","network"],"backgroundTag":"container-registry-push-failed","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}