AlistGo/alist · error

faild to get ETag from header

Error message

faild to get ETag from header

What it means

In Cloudreve S3-like policy upload, a chunk PUT returned HTTP 200 but the response had no ETag header, so the driver cannot assemble the multipart completion manifest. ETags are required to complete an S3 multipart upload.

Source

Thrown at drivers/cloudreve/util.go:414

		req.ContentLength = byteSize
		finish += byteSize
		res, err := base.HttpClient.Do(req)
		if err != nil {
			return err
		}
		etag := res.Header.Get("ETag")
		res.Body.Close()
		switch {
		case res.StatusCode != 200:
			retryCount++
			if retryCount > maxRetries {
				return fmt.Errorf("upload failed after %d retries due to server errors, error %d", maxRetries, res.StatusCode)
			}
			backoff := time.Duration(1<<retryCount) * time.Second
			utils.Log.Warnf("[Cloudreve-S3] server errors %d while uploading, retrying after %v...", res.StatusCode, backoff)
			time.Sleep(backoff)
		case etag == "":
			return errors.New("faild to get ETag from header")
		default:
			retryCount = 0
			etags = append(etags, etag)
			finish += byteSize
			up(float64(finish) * 100 / float64(stream.GetSize()))
			chunk++
		}
	}

	// s3LikeFinishUpload
	// https://github.com/cloudreve/frontend/blob/b485bf297974cbe4834d2e8e744ae7b7e5b2ad39/src/component/Uploader/core/api/index.ts#L204-L252
	bodyBuilder := &strings.Builder{}
	bodyBuilder.WriteString("<CompleteMultipartUpload>")
	for i, etag := range etags {
		bodyBuilder.WriteString(fmt.Sprintf(
			`<Part><PartNumber>%d</PartNumber><ETag>%s</ETag></Part>`,
			i+1, // PartNumber 从 1 开始
			etag,

View on GitHub (pinned to 843d9dc814)

Solutions

  1. Verify with a manual multipart PUT (curl -X PUT with -D -) whether the endpoint really returns ETag; if not, switch the Cloudrebve storage policy to a provider that does.
  2. Remove any proxy/CDN layer in front of the S3 endpoint that could strip the ETag response header.
  3. Update the S3-compatible server (MinIO/Ceph) to a version known to return ETag for UploadPart.
  4. As a workaround, use a different upload policy (local or OneDrive) in Cloudreve for this storage.

Example fix

# verify ETag presence on chunk upload
curl -sS -D - -o /dev/null -X PUT \
  -H 'Authorization: ...' \
  --data-binary @chunk.bin \
  'https://s3.example.com/bucket/key?partNumber=1&uploadId=...'
# -> HTTP/1.1 200 OK
# -> ETag: "abc123..."   (must be present)
Defensive patterns

Strategy: validation

Validate before calling

// before switching a policy to S3-like, confirm the backend returns ETag on part PUT
resp, err := http.NewRequest(http.MethodPut, partURL, bytes.NewReader(chunk))
// ... execute ...
if resp.Header.Get("ETag") == "" {
    return errors.New("backend strips ETag; multipart completion will fail — pick another endpoint")
}

Try / catch

etag := res.Header.Get("ETag")
if etag == "" {
    if sc := res.Header.Get("X-Amz-Etag"); sc != "" { etag = sc } // some clones use alt casing/keys
}
if etag == "" {
    return errors.New("faild to get ETag from header")
}

Prevention

When it happens

Trigger: upS3-like loop: res.Header.Get("ETag") == "" with StatusCode 200. Happens when the S3-compatible backend (or a proxy/CDN in front of it) omits ETag on PutObject/UploadPart responses.

Common situations: Non-AWS S3 clones (some MinIO gateway modes, certain object storage vendors) that strip or never set ETag; proxy (nginx/Cloudflare) dropping the header; servers that only return ETag when the request supplies Content-MD5.

Related errors


AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15). Data as JSON: /api/errors/8602758953275846. Report an issue: GitHub.