theonedev/onedev · error · ClientException

Digest mismatch

Error message

Digest mismatch

What it means

After uploading the file bytes, packBlobService.uploadBlob(projectId, is, sha256Hash) returns null when the SHA-256 digest of the received content does not match the sha256 attribute supplied by the client. The handler then throws a ClientException (HTTP 400) 'Digest mismatch'.

Source

Thrown at server-plugin/server-plugin-pack-pypi/src/main/java/io/onedev/server/plugin/pack/pypi/PypiPackHandler.java:147

								LockUtils.run(getLockName(projectId, name), () -> transactionService.run(() -> {
									var project = checkProject(projectId, true);
									var contentDisposition = item.getHeaders().getHeader("content-disposition"); 
									if (contentDisposition == null)
										throw new ClientException(SC_BAD_REQUEST, "Content disposition header not found in uploaded file");
									String fileName = null;
									for (var field: Splitter.on(";").omitEmptyStrings().trimResults().split(contentDisposition)) {
										if (field.startsWith("filename=")) {
											fileName = field.substring("filename=".length() + 1);
											fileName = fileName.substring(0, fileName.length() - 1);
											break;
										}
									}
									if (fileName == null) 
										throw new ClientException(SC_BAD_REQUEST, "File name not found in content disposition header of uploaded file");

									var packBlobId = packBlobService.uploadBlob(projectId, is, sha256Hash);																																								
									if (packBlobId == null)
										throw new ClientException(SC_BAD_REQUEST, "Digest mismatch");
									
									PypiData data;
									var pack = packService.findByNameAndVersion(project, TYPE, name, version);
									if (pack == null) {
										pack = new Pack();
										pack.setType(TYPE);
										pack.setName(name);
										pack.setVersion(version);
										pack.setProject(project);
										data = new PypiData(attributes, new LinkedHashMap<>());
										pack.setData(data);
									} else {
										data = (PypiData) pack.getData();
									}

									Build build = null;
									if (buildId != null)
										build = buildService.load(buildId);

View on GitHub (pinned to d44925c47c)

Solutions

  1. Recompute sha256 on the exact file being uploaded (e.g. sha256sum pkg.whl) and resend with the corrected value.
  2. Ensure the upload transfers bytes unchanged (binary mode; no proxy body transformation).
  3. Make sure the file attached to the request is the same artifact the hash was computed from (same build output).

Example fix

// before
-H "sha256: $(sha256sum old.whl | cut -d' ' -f1)" -F "file=@new.whl"
// after
SHA=$(sha256sum new.whl | cut -d' ' -f1)
curl -F "sha256=$SHA" -F "file=@new.whl" http://server/~pypi/upload
Defensive patterns

Strategy: validation

Validate before calling

import hashlib
expected = hashlib.sha256(open('pkg.whl','rb').read()).hexdigest()
assert expected == declared_sha256, 'hash computed on the wrong artifact'

Try / catch

if resp.status_code == 400 and 'Digest mismatch' in resp.text:
    recompute_and_resend()

Prevention

When it happens

Trigger: Publishing a PyPI package where the declared sha256 form field does not equal the actual SHA-256 of the uploaded file bytes (recomputed server-side).

Common situations: Computing the hash before a tool re-writes/re-compresses the file; sending the digest of a different file than the one attached; text-mode corruption of the file during transfer; stale hash from an earlier build artifact.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


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