{"record":{"id":"764c4910f84543ce","repo":"jdx/mise","slug":"starting-blob-upload-failed","errorCode":null,"errorMessage":"starting blob upload failed: {} {}{}","messagePattern":"starting blob upload failed: (.+?) (.+?)(.+?)","errorType":"http","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"src/oci/registry.rs","lineNumber":1321,"sourceCode":"                let mut rb = HTTP\n                    .reqwest()?\n                    .post(start_url.as_str())\n                    .header(\"Content-Length\", \"0\");\n                if let Some(a) = auth {\n                    rb = rb.header(\"Authorization\", a);\n                }\n                Ok(rb)\n            })\n            .await\n            .wrap_err_with(|| format!(\"POST {start_url}\"))?;\n        let status = resp.status();\n        match status {\n            StatusCode::CREATED => return Ok(UploadOutcome::Mounted),\n            StatusCode::ACCEPTED => {}\n            s => {\n                // Let transient statuses bubble as retryable errors.\n                resp.error_for_status_ref()?;\n                bail!(\n                    \"starting blob upload failed: {} {}{}\",\n                    s.as_u16(),\n                    start_url,\n                    push_auth_hint(s, had_credential),\n                );\n            }\n        }\n        let mut location = self.resolve_location(&resp)?;\n        pr.set_position(0);\n\n        // 2. Transfer the bytes.\n        if size > UPLOAD_CHUNK_SIZE {\n            // Chunked: PATCH each segment, then a zero-length finalizing PUT.\n            let mut offset = 0u64;\n            while offset < size {\n                let len = UPLOAD_CHUNK_SIZE.min(size - offset);\n                let err_slot: UploadErrSlot = Default::default();\n                let resp = self","sourceCodeStart":1303,"sourceCodeEnd":1339,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/oci/registry.rs#L1303-L1339","documentation":"First step of the OCI blob upload protocol: a POST to initiate an upload session, expecting 202 Accepted (or 201 Created when the blob was cross-repo mounted). Any other non-transient status fails here, decorated with push_auth_hint — either \"no credentials found; run docker login\" or \"credentials rejected or lack push permission (ghcr.io needs write:packages)\" plus the response body.","triggerScenarios":"Pushing without `docker login` to the destination registry (401); ghcr.io token with only read:packages (403); read-only registry credentials; expired stored credentials; registry quotas or denied repository creation.","commonSituations":"CI pushing to ghcr.io with a PAT missing the write:packages scope; org registries where the account lacks push rights to the repo namespace; first push to a brand-new repository on a registry with restrictive policies.","solutions":["Run `docker login <registry>` (or `podman login`) with credentials that have push permission for the target namespace","For ghcr.io: use a classic PAT with `write:packages` (and `read:packages`), and confirm the package/repo allows your account to write","Sanity-check push rights by pushing a tiny test tag with docker: `docker push <registry>/<repo>:ci-smoke`","Read the body line for the registry's error code (DENIED, NAME_UNKNOWN on ECR requiring pre-created repos, quotas)"],"exampleFix":"# before — CI job pushes without auth\nmise oci push registry.example.com/acme/app:1\n\n# after — log in with a push-scoped token first\ndocker login registry.example.com -u $REG_USER -p $REG_TOKEN\nmise oci push registry.example.com/acme/app:1","handlingStrategy":"validation","validationCode":"# Prove push credentials work before the build/push job:\ndocker login registry.example.com -u \"$REG_USER\" -p \"$REG_TOKEN\"\ndocker push registry.example.com/acme/ci-smoke:latest  # tiny throwaway image\n# ghcr.io: create the PAT with write:packages + read:packages scopes.","typeGuard":null,"tryCatchPattern":"// Branch on the auth hint embedded in the message:\nlet msg = String::from_utf8_lossy(&out.stderr);\nif msg.contains(\"starting blob upload failed\") {\n    if msg.contains(\"no credentials were found\") {\n        // run docker login, then retry the push once\n    } else if msg.contains(\"rejected or lack push permission\") {\n        // fix token scopes (ghcr: write:packages) or repo ACLs — retrying won't help\n    }\n}","preventionTips":["Verify push scopes at CI start with a tiny smoke push, not during the real build","For ghcr.io, use a PAT with write:packages; check org package settings grant write access","Pre-create repositories on registries that require it (AWS ECR)"],"tags":["mise","oci","registry","push","auth","upload","permissions"],"backgroundTag":"container-registry-push-failed","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}