AlistGo/alist · error

finish upload failed: %s

Error message

finish upload failed: %s

What it means

Returned by completeMultipartUpload when the 'phase=finish&uploadmode=part' POST answers with a code other than 2000 (success) or 4024 (merge-in-progress). The server refused to finalize the merged object; message is the server's reason. Note 4024 is intentionally tolerated because merging is asynchronous.

Source

Thrown at drivers/doubao/util.go:742

	body := _convertUploadParts(parts)

	err := utils.Retry(MaxRetryAttempts, time.Second, func() (err error) {
		_, err = d.uploadRequest(uploadUrl, http.MethodPost, storeInfo, func(req *resty.Request) {
			req.SetQueryParams(map[string]string{
				"uploadid":   uploadID,
				"phase":      "finish",
				"uploadmode": "part",
			})
			req.SetBody(body)
		}, &uploadResp)

		if err != nil {
			return err
		}
		// 检查响应状态码 2000 成功 4024 分片合并中
		if uploadResp.Code != 2000 && uploadResp.Code != 4024 {
			return fmt.Errorf("finish upload failed: %s", uploadResp.Message)
		}

		return err
	})

	if err != nil {
		return fmt.Errorf("failed to complete multipart upload: %w", err)
	}

	return nil
}

func (d *Doubao) commitMultipartUpload(uploadConfig *UploadConfig) error {
	uploadUrl := d.UploadToken.Samantha.UploadInfo.VideoHost
	params := map[string]string{
		"Action":    "CommitUploadInner",
		"Version":   "2020-11-19",
		"SpaceName": d.UploadToken.Samantha.UploadInfo.SpaceName,

View on GitHub (pinned to 843d9dc814)

Solutions

  1. Inspect the server message — it typically says which parts are missing or whether the session is invalid
  2. If the session expired, restart the whole upload with a fresh init
  3. Verify all parts returned non-empty Etags before finish (a skipped/failed part with empty etag corrupts the finish body)
  4. Retry after backoff; 4024-adjacent states sometimes settle into 2000
  5. Check driver updates for changes to _convertUploadParts or the finish query params
Defensive patterns

Strategy: retry

Validate before calling

// verify every part has a non-empty Etag before finishing
for i, p := range parts {
    if p == nil || p.Etag == "" {
        return fmt.Errorf("part %d missing etag; refusing to finish", i+1)
    }
}

Try / catch

if err := d.completeMultipartUpload(...); err != nil {
    if strings.Contains(err.Error(), "finish upload failed") {
        time.Sleep(5 * time.Second) // give async merge (4024) time to settle
        return d.completeMultipartUpload(...)
    }
    return err
}

Prevention

When it happens

Trigger: Finishing a multipart upload where some parts were never received server-side despite client-side success, the upload session expired before finish, wrong uploadid, or the finish body (_convertUploadParts serialization) doesn't match the server's record of parts.

Common situations: A part upload silently failed earlier (etag mismatch not caught), very long uploads where the session times out server-side, retry of finish after partial server state, or Doubao changing the finish contract so the parts list encoding is rejected.

Related errors


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