theonedev/onedev · error · ClientException
DIGEST_INVALID
DIGEST_INVALID
Error message
Digest expected to finish blob upload
What it means
Finalizing a chunked blob upload requires a POST to the upload URL with a digest query parameter identifying the complete blob. This error is thrown when the finalizing POST omits the digest parameter; the registry cannot verify what the assembled blob should hash to, so it rejects the request with 400 and code DIGEST_INVALID.
Source
Thrown at server-plugin/server-plugin-pack-container/src/main/java/io/onedev/server/plugin/pack/container/ContainerServlet.java:213
break;
}
case "PUT": {
if (packBlobService.getUploadFileSize(projectId, uuid) == -1)
throw new NotFoundException(ErrorCode.BLOB_UPLOAD_UNKNOWN);
var contentLength = request.getHeader("Content-Length");
if (contentLength != null) {
var parsedContentLength = parseLong(contentLength);
if (parsedContentLength != 0) {
try (var is = request.getInputStream()) {
packBlobService.uploadBlob(projectId, uuid, is);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
var digestString = request.getParameter("digest");
if (digestString == null) {
throw new ClientException(SC_BAD_REQUEST, ErrorCode.DIGEST_INVALID,
"Digest expected to finish blob upload");
}
var digest = parseDigest(digestString);
if (packBlobService.finishUpload(projectId, uuid, digest.getHash()) != null) {
response.setStatus(SC_CREATED);
response.setHeader("Location", getBlobUrl(projectPath, repository, digestString));
response.setHeader("Docker-Content-Digest", digestString);
} else {
throw new ClientException(SC_BAD_REQUEST, ErrorCode.DIGEST_INVALID,
"Invalid blob digest");
}
break;
}
case "GET": {
var uploadedSize = packBlobService.getUploadFileSize(projectId, uuid);
if (uploadedSize == -1)
throw new NotFoundException(ErrorCode.BLOB_UPLOAD_UNKNOWN);View on GitHub (pinned to d44925c47c)
Solutions
- Send the finalize request as POST to the Location URL with ?digest=sha256:<hex> matching the full blob digest.
- Compute the digest over the entire blob content (all chunks concatenated), not per-chunk digests.
- Verify the URL retains its query string across redirects (configure the HTTP client to preserve it).
- If the client cannot compute digests, switch to single-POST monolithic upload where the server computes it.
Example fix
// before post(uploadLocation); // no digest // after post(uploadLocation + "?digest=" + URLEncoder.encode(digest, UTF_8));
Defensive patterns
Strategy: validation
Validate before calling
const url = new URL(uploadLocation);
if (!url.searchParams.get('digest')) {
url.searchParams.set('digest', fullBlobDigest); // sha256:<hex of entire blob>
}
// request finalization with url.toString() Try / catch
catch (err) {
if (err.status === 400 && err.code === 'DIGEST_INVALID' && /Digest expected/.test(err.message)) {
await finalizeUpload(uploadLocation, computeDigest(entireBlob));
} else throw err;
} Prevention
- Always append ?digest= to the finalize POST URL.
- Compute the digest over all chunks concatenated, not per chunk.
- Preserve query strings across HTTP redirects.
- Use monolithic single-POST upload if digest computation is impractical.
When it happens
Trigger: POST /v2/<name>/blobs/uploads/<uuid>?digest=<digest> sent without the digest query parameter while uploadedSize > 0. Also: clients that send the digest as a header or body instead of a query parameter; URL-encoded clients dropping the query string on redirect.
Common situations: Custom scripts finishing an upload with plain POST (no ?digest=); clients following a 3xx redirect that strips the query string; hand-rolled OCI tooling missing the finalization step's parameter; API wrappers that build the finalize URL incorrectly.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- BLOB_UPLOAD_INVALID
- Invalid http method for blob upload: ${method}
- Invalid http method for blob pull: ${method}
- DENIED
- Invalid http method for manifest pull: ${method}
AI-assisted analysis of theonedev/onedev@d44925c47c (2026-09-06).
Data as JSON: /api/errors/c811f1d440e55581.
Report an issue: GitHub.