restic/restic · error

download error: %w

Error message

download error: %w

What it means

The same pack streaming path as the partial case, but here the pack could not be downloaded at all. restic deliberately returns a plain wrapped download error rather than ErrPackData, because the check command's repair suggestions are pointless when no data could be read.

Source

Thrown at internal/repository/checker.go:467

			return &partialReadError{err}
		}

		hash = restic.IDFromHash(hrd.Sum(nil))
		return nil
	})
	errs = append(errs, blobErrors...)
	if err != nil {
		var e *partialReadError
		isPartialReadError := errors.As(err, &e)
		// failed to load the pack file, return as further checks cannot succeed anyways
		debug.Log("  error streaming pack (partial %v): %v", isPartialReadError, err)
		if isPartialReadError {
			return &ErrPackData{PackID: id, errs: append(errs, fmt.Errorf("partial download error: %w", err))}
		}

		// The check command suggests to repair files for which a `ErrPackData` is returned. However, this file
		// completely failed to download such that there's no point in repairing anything.
		return fmt.Errorf("download error: %w", err)
	}
	if !hash.Equal(id) {
		debug.Log("pack ID does not match, want %v, got %v", id, hash)
		return &ErrPackData{PackID: id, errs: append(errs, errors.Errorf("unexpected pack id %v", hash))}
	}

	blobs, hdrSize, err := pack.List(r.Key(), bytes.NewReader(hdrBuf), int64(len(hdrBuf)))
	if err != nil {
		return &ErrPackData{PackID: id, errs: append(errs, err)}
	}

	if uint32(idxHdrSize) != hdrSize {
		debug.Log("Pack header size does not match, want %v, got %v", idxHdrSize, hdrSize)
		errs = append(errs, errors.Errorf("pack header size does not match, want %v, got %v", idxHdrSize, hdrSize))
	}

	for _, blob := range blobs {
		// Check if blob is contained in index and position is correct

View on GitHub (pinned to a80be1478a)

Solutions

  1. Verify the backend is reachable and credentials are valid (restic list keys is a cheap probe)
  2. Re-run the check once transport is healthy
  3. If the object is permanently gone, restore the repository from another copy or re-backup the source data
  4. Check whether a crashed concurrent prune left the repository half-modified, then run restic check
Defensive patterns

Strategy: retry

Validate before calling

// cheap reachability probe before a long check run
if _, err := repo.List(ctx, restic.KeyFile); err != nil {
    return fmt.Errorf("backend not ready, aborting check: %w", err)
}

Type guard

func isDownloadError(err error) bool {
    var pd *repository.ErrPackData
    return err != nil && !errors.As(err, &pd) && strings.Contains(err.Error(), "download error")
}

Try / catch

err := checkPack(ctx, r, id, blobs, size, bufRd, dec)
if err != nil {
    var pd *repository.ErrPackData
    if errors.As(err, &pd) {
        reportCorruption(pd.PackID) // partial data: repair path
    } else {
        return retryLater(err) // full download failure: retry once backend is healthy
    }
}

Prevention

When it happens

Trigger: Backend unreachable or authentication rejected mid-run; the pack object deleted or unreadable; DNS or TLS failures; a repository being concurrently reorganized, for example by an interrupted prune.

Common situations: Expired tokens during long checks; object-storage outages; objects removed by another process; wrong permissions on specific prefixes.

Related errors


AI-assisted analysis of restic/restic@a80be1478a (2026-08-15). Data as JSON: /api/errors/9e84681cc45f9a0e. Report an issue: GitHub.