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

  1. Send the finalize request as POST to the Location URL with ?digest=sha256:<hex> matching the full blob digest.
  2. Compute the digest over the entire blob content (all chunks concatenated), not per-chunk digests.
  3. Verify the URL retains its query string across redirects (configure the HTTP client to preserve it).
  4. 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

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


AI-assisted analysis of theonedev/onedev@d44925c47c (2026-09-06). Data as JSON: /api/errors/c811f1d440e55581. Report an issue: GitHub.