theonedev/onedev · error · ClientException
BLOB_UPLOAD_INVALID
BLOB_UPLOAD_INVALID
Error message
Invalid chunk range
What it means
This error is thrown during a chunked (PATCH) docker registry blob upload when the Content-Range header declares a start offset that does not match the number of bytes already uploaded to the server. The registry responds with 416 (Requested Range Not Satisfiable) and a Range header telling the client where to resume. It enforces the OCI distribution spec requirement that chunks be appended strictly in order with no gaps or overlaps.
Source
Thrown at server-plugin/server-plugin-pack-container/src/main/java/io/onedev/server/plugin/pack/container/ContainerServlet.java:181
});
} else if ((matcher = compile("(.+)/([^/]+)/blobs/uploads/([^/]+)").matcher(pathInfo)).matches()) {
var projectPath = matcher.group(1);
var repository = matcher.group(2);
var uuid = matcher.group(3);
response.setHeader("Location", getUploadUrl(projectPath, repository, uuid));
response.setHeader("Docker-Upload-UUID", uuid);
var projectId = sessionService.call(() -> checkProject(projectPath, true).getId());
switch (method) {
case "PATCH": {
var uploadedSize = packBlobService.getUploadFileSize(projectId, uuid);
if (uploadedSize == -1)
throw new NotFoundException(ErrorCode.BLOB_UPLOAD_UNKNOWN);
var contentRange = request.getHeader("Content-Range");
if (contentRange != null) {
var chunkBegin = parseLong(substringBefore(contentRange, "-"));
if (uploadedSize != chunkBegin) {
response.setHeader("Range", "0-" + (uploadedSize - 1));
throw new ClientException(SC_REQUESTED_RANGE_NOT_SATISFIABLE,
ErrorCode.BLOB_UPLOAD_INVALID, "Invalid chunk range");
}
} else if (uploadedSize != 0) {
throw new ClientException(SC_REQUESTED_RANGE_NOT_SATISFIABLE, ErrorCode.BLOB_UPLOAD_INVALID,
"Content range header expected after first upload");
}
try (var is = request.getInputStream()) {
uploadedSize += packBlobService.uploadBlob(projectId, uuid, is);
} catch (IOException e) {
throw new RuntimeException(e);
}
response.setStatus(SC_ACCEPTED);
response.setHeader("Range", "0-" + (uploadedSize - 1));
break;
}
case "PUT": {
if (packBlobService.getUploadFileSize(projectId, uuid) == -1)
throw new NotFoundException(ErrorCode.BLOB_UPLOAD_UNKNOWN);View on GitHub (pinned to d44925c47c)
Solutions
- Read the Range response header from the 416 reply and re-issue the PATCH with Content-Range starting at uploadedSize (end of server's Range).
- Do not upload chunks in parallel or out of order; append strictly sequentially.
- After any failure, issue a GET on the upload URL to learn the authoritative uploaded size before resuming.
- Reconfigure HTTP clients/proxies not to transparently retry PATCH requests with the original body/headers.
Example fix
// before
request.setHeader("Content-Range", "0-" + (file.length() - 1)); // always full range
// after
long uploaded = parseRangeEndFromResponse(lastResponse.getHeader("Range")) + 1;
request.setHeader("Content-Range", uploaded + "-" + (uploaded + chunk.length - 1)); Defensive patterns
Strategy: validation
Validate before calling
const range = res.headers.get('Range'); // '0-<uploadedSize-1>' on 416
const uploaded = range ? parseInt(range.split('-')[1], 10) + 1 : 0;
if (chunkStart !== uploaded) {
chunkStart = uploaded; // re-align chunk offset before re-PATCH
} Try / catch
catch (err) {
if (err.status === 416 && err.code === 'BLOB_UPLOAD_INVALID') {
const uploaded = getUploadStatus(uploadUrl); // GET returns Range
resumeUploadFrom(uploaded);
} else throw err;
} Prevention
- Always derive Content-Range from the server's Range header, not local file offsets.
- Upload chunks strictly sequentially; never parallelize PATCHes to one session.
- After any failure, GET the upload URL to re-sync state before resuming.
- Disable transparent HTTP-client retries for PATCH requests.
When it happens
Trigger: PATCH request to /v2/<name>/blobs/uploads/<uuid> whose Content-Range header (e.g. 'Content-Range: 500-999') has a begin offset != the bytes already stored (uploadedSize). Also fired when a client retries a chunk from the wrong offset after a network interruption, or sends a stale Content-Range after another chunk already landed.
Common situations: Docker/podman/kaniko pushes over flaky networks where a chunk is retried with a stale offset; parallel chunk uploads racing each other so offsets no longer match server state; custom OCI clients computing Content-Range from local file position instead of the server's returned Range header; proxy/load-balancer retrying a PATCH against a different backend.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- DIGEST_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/df50c1d12bf05493.
Report an issue: GitHub.