{"record":{"id":"36bef7bc23ad43b6","repo":"AlistGo/alist","slug":"failed-to-finish-download-w","errorCode":null,"errorMessage":"failed to finish download: %w","messagePattern":"failed to finish download: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"drivers/halalcloud/util.go","lineNumber":347,"sourceCode":"\t}\n\tn = copy(p, *oo.chunk)\n\t*oo.chunk = (*oo.chunk)[n:]\n\n\too.shaTemp.Write(*oo.chunk)\n\n\treturn n, nil\n}\n\n// Close closed the file - MAC errors are reported here\nfunc (oo *openObject) Close() (err error) {\n\too.mu.Lock()\n\tdefer oo.mu.Unlock()\n\tif oo.closed {\n\t\treturn nil\n\t}\n\t// 校验Sha1\n\tif string(oo.shaTemp.Sum(nil)) != oo.sha {\n\t\treturn fmt.Errorf(\"failed to finish download: %w\", err)\n\t}\n\n\too.closed = true\n\treturn nil\n}\n\nfunc GetMD5Hash(text string) string {\n\ttHash := md5.Sum([]byte(text))\n\treturn hex.EncodeToString(tHash[:])\n}\n\n// chunkSize describes a size and position of chunk\ntype chunkSize struct {\n\tposition int64\n\tsize     int\n}\n\nfunc getChunkSizes(sliceSize []*pubUserFile.SliceSize) (chunks []chunkSize) {","sourceCodeStart":329,"sourceCodeEnd":365,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/drivers/halalcloud/util.go#L329-L365","documentation":"Returned by openObject.Close when the running SHA-1 of all consumed bytes does not equal the expected hash — the download's integrity check fires at close time. Note a defect in the message: it wraps `%w` with `err`, which is Close's named return and is nil at that point, so the emitted error carries no cause. The real information (expected vs computed hash) is not included.","triggerScenarios":"Fewer bytes read than the file contains (early termination of io.Copy, wrapped LimitReader cutting the stream, context cancellation mid-copy) so shaTemp never accumulated the full digest; or genuinely corrupted transfer.","commonSituations":"Clients that stop reading early (content-length mismatch handling, ranged reads on a non-range-aware consumer); a length wrapper (LimitedReadCloser) truncating reads; bit-flips in transit when transport lacked integrity.","solutions":["Ensure the stream is fully drained to EOF before Close — partial reads are the dominant cause of a mismatched SHA-1 here.","If you intentionally read a prefix, you cannot reuse this object's close-time check; read the whole file or construct the object without hash verification.","On a genuine mismatch after a full read, retry the download from scratch (the data is corrupt).","Upgrade the driver so the error embeds computed vs expected hashes (%x of shaTemp.Sum vs oo.sha) for diagnosability."],"exampleFix":"// before (driver): wraps nil err, loses info\nreturn fmt.Errorf(\"failed to finish download: %w\", err)\n\n// after: report the actual mismatch\nreturn fmt.Errorf(\"failed to finish download: sha1 mismatch, want %x got %x\", oo.sha, oo.shaTemp.Sum(nil))","handlingStrategy":"validation","validationCode":"// drain fully so the close-time sha1 check has all bytes\nwritten, cerr := io.Copy(dst, stream)\nif cerr == nil && written == expectedSize {\n    return stream.Close()\n}","typeGuard":null,"tryCatchPattern":"if err := stream.Close(); err != nil && strings.Contains(err.Error(), \"failed to finish download\") {\n    // partial read or corruption: re-download from scratch\n    return redownload(ctx, fileID)\n}","preventionTips":["Always read to EOF before Close","Never wrap the stream in a short LimitReader when the hash check is active","Retry full downloads on hash mismatch"],"tags":["halalcloud","sha1","integrity","partial-read","close-timeout"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}