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 correctView on GitHub (pinned to a80be1478a)
Solutions
- Verify the backend is reachable and credentials are valid (restic list keys is a cheap probe)
- Re-run the check once transport is healthy
- If the object is permanently gone, restore the repository from another copy or re-backup the source data
- 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
- Verify credentials and connectivity before starting long check runs
- Avoid running prune and check concurrently on the same repository
- Use exponential backoff around backend outages instead of tight retries
- Keep an offsite copy so permanently unreadable objects are recoverable
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
- partial download error: %w
- LoadUnpacked(%v): %w
- load config file failed: %w
- azure: invalid format
- azure: invalid format: bucket name or path not found
AI-assisted analysis of restic/restic@a80be1478a (2026-08-15).
Data as JSON: /api/errors/9e84681cc45f9a0e.
Report an issue: GitHub.