AlistGo/alist · error

failed to finish download: %w

Error message

failed to finish download: %w

What it means

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.

Source

Thrown at drivers/halalcloud/util.go:347

	}
	n = copy(p, *oo.chunk)
	*oo.chunk = (*oo.chunk)[n:]

	oo.shaTemp.Write(*oo.chunk)

	return n, nil
}

// Close closed the file - MAC errors are reported here
func (oo *openObject) Close() (err error) {
	oo.mu.Lock()
	defer oo.mu.Unlock()
	if oo.closed {
		return nil
	}
	// 校验Sha1
	if string(oo.shaTemp.Sum(nil)) != oo.sha {
		return fmt.Errorf("failed to finish download: %w", err)
	}

	oo.closed = true
	return nil
}

func GetMD5Hash(text string) string {
	tHash := md5.Sum([]byte(text))
	return hex.EncodeToString(tHash[:])
}

// chunkSize describes a size and position of chunk
type chunkSize struct {
	position int64
	size     int
}

func getChunkSizes(sliceSize []*pubUserFile.SliceSize) (chunks []chunkSize) {

View on GitHub (pinned to 843d9dc814)

Solutions

  1. Ensure the stream is fully drained to EOF before Close — partial reads are the dominant cause of a mismatched SHA-1 here.
  2. 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.
  3. On a genuine mismatch after a full read, retry the download from scratch (the data is corrupt).
  4. Upgrade the driver so the error embeds computed vs expected hashes (%x of shaTemp.Sum vs oo.sha) for diagnosability.

Example fix

// before (driver): wraps nil err, loses info
return fmt.Errorf("failed to finish download: %w", err)

// after: report the actual mismatch
return fmt.Errorf("failed to finish download: sha1 mismatch, want %x got %x", oo.sha, oo.shaTemp.Sum(nil))
Defensive patterns

Strategy: validation

Validate before calling

// drain fully so the close-time sha1 check has all bytes
written, cerr := io.Copy(dst, stream)
if cerr == nil && written == expectedSize {
    return stream.Close()
}

Try / catch

if err := stream.Close(); err != nil && strings.Contains(err.Error(), "failed to finish download") {
    // partial read or corruption: re-download from scratch
    return redownload(ctx, fileID)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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