{"record":{"id":"ed72a643f8e337d7","repo":"AlistGo/alist","slug":"finish-upload-failed-s","errorCode":null,"errorMessage":"finish upload failed: %s","messagePattern":"finish upload failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"drivers/doubao/util.go","lineNumber":742,"sourceCode":"\n\tbody := _convertUploadParts(parts)\n\n\terr := utils.Retry(MaxRetryAttempts, time.Second, func() (err error) {\n\t\t_, err = d.uploadRequest(uploadUrl, http.MethodPost, storeInfo, func(req *resty.Request) {\n\t\t\treq.SetQueryParams(map[string]string{\n\t\t\t\t\"uploadid\":   uploadID,\n\t\t\t\t\"phase\":      \"finish\",\n\t\t\t\t\"uploadmode\": \"part\",\n\t\t\t})\n\t\t\treq.SetBody(body)\n\t\t}, &uploadResp)\n\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// 检查响应状态码 2000 成功 4024 分片合并中\n\t\tif uploadResp.Code != 2000 && uploadResp.Code != 4024 {\n\t\t\treturn fmt.Errorf(\"finish upload failed: %s\", uploadResp.Message)\n\t\t}\n\n\t\treturn err\n\t})\n\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to complete multipart upload: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (d *Doubao) commitMultipartUpload(uploadConfig *UploadConfig) error {\n\tuploadUrl := d.UploadToken.Samantha.UploadInfo.VideoHost\n\tparams := map[string]string{\n\t\t\"Action\":    \"CommitUploadInner\",\n\t\t\"Version\":   \"2020-11-19\",\n\t\t\"SpaceName\": d.UploadToken.Samantha.UploadInfo.SpaceName,","sourceCodeStart":724,"sourceCodeEnd":760,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/drivers/doubao/util.go#L724-L760","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Inspect the server message — it typically says which parts are missing or whether the session is invalid","If the session expired, restart the whole upload with a fresh init","Verify all parts returned non-empty Etags before finish (a skipped/failed part with empty etag corrupts the finish body)","Retry after backoff; 4024-adjacent states sometimes settle into 2000","Check driver updates for changes to _convertUploadParts or the finish query params"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// verify every part has a non-empty Etag before finishing\nfor i, p := range parts {\n    if p == nil || p.Etag == \"\" {\n        return fmt.Errorf(\"part %d missing etag; refusing to finish\", i+1)\n    }\n}","typeGuard":null,"tryCatchPattern":"if err := d.completeMultipartUpload(...); err != nil {\n    if strings.Contains(err.Error(), \"finish upload failed\") {\n        time.Sleep(5 * time.Second) // give async merge (4024) time to settle\n        return d.completeMultipartUpload(...)\n    }\n    return err\n}","preventionTips":["Never finish with gaps in the parts array","Retry finish once after a delay — async merge states (4024) settle to 2000","Don't re-init the same upload session from another client mid-flight"],"tags":["doubao","upload","multipart","finish","api-error","go"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}