restic/restic · error

decompressing blob %v from pack %v failed: %w

Error message

decompressing blob %v from pack %v failed: %w

What it means

zstd DecodeAll failed on a blob whose header entry has the compression flag set, so after successful decryption the payload is not valid zstd data. This means the compressed plaintext is damaged (bitrot that still decrypts is rare because MAC failure would fire first, so typically the entry flags and the stored bytes disagree) or the blob was written uncompressed while flagged compressed. Thrown from packBlobIterator.Next during any pack-reading operation.

Source

Thrown at internal/repository/repository.go:1334

	if int(entry.Length) <= b.key.NonceSize() {
		debug.Log("%v", b.blobs)
		return packBlobValue{}, fmt.Errorf("invalid blob length %v", entry)
	}

	// decryption errors are likely permanent, give the caller a chance to skip them
	nonce, ciphertext := buf[:b.key.NonceSize()], buf[b.key.NonceSize():]
	plaintext, err := b.key.Open(ciphertext[:0], nonce, ciphertext, nil)
	if err != nil {
		err = fmt.Errorf("decrypting blob %v from pack %v failed: %w", h, b.packID.String(), err)
	}
	if err == nil && entry.IsCompressed() {
		// DecodeAll will allocate a slice if it is not large enough since it
		// knows the decompressed size (because we're using EncodeAll)
		b.decode, err = b.dec.DecodeAll(plaintext, b.decode[:0])
		plaintext = b.decode
		if err != nil {
			err = fmt.Errorf("decompressing blob %v from pack %v failed: %w", h, b.packID.String(), err)
		}
	}
	if err == nil {
		id := restic.Hash(plaintext)
		if !id.Equal(entry.ID) {
			debug.Log("read blob %v/%v from pack %v: wrong data returned, hash is %v",
				h.Type, h.ID, b.packID.String(), id)
			err = fmt.Errorf("read blob %v from pack %v: wrong data returned, hash is %v",
				h, b.packID.String(), id)
		}
	}

	return packBlobValue{entry.BlobHandle, plaintext, err}, nil
}

func (r *Repository) zeroChunk() restic.ID {
	r.zeroChunkOnce.Do(func() {
		r.zeroChunkID = restic.Hash(make([]byte, chunker.MinSize))

View on GitHub (pinned to a80be1478a)

Solutions

  1. Run restic check --read-data to identify the affected packs
  2. Rebuild the index (restic rebuild-index) so entry metadata matches actual pack headers
  3. Recover the pack from a second copy or backend versioning
  4. Re-backup affected data and prune the damaged pack
Defensive patterns

Strategy: try-catch

Try / catch

if err != nil && strings.Contains(err.Error(), "decompressing blob") {
    // entry metadata disagrees with stored bytes; rebuild index and re-verify
    log.Printf("pack %s: blob %v fails zstd decode — run restic rebuild-index then restic check", packID, h.ID.Str())
    return nil // skip in scan mode
}

Prevention

When it happens

Trigger: A pack header entry flagged IsCompressed whose blob bytes are raw/uncompressed (mixed-version writer bug, manual pack surgery); corruption of the blob body in a way that preserves authentication is not possible under the MAC, so in practice the entry metadata is the corrupted part; reading v2-compressed packs with a mismatched index.

Common situations: Repositories touched by prerelease or mismatched restic versions during the compression feature rollout; indexes rebuilt incorrectly so blob lengths/flags point into the middle of another blob.

Related errors


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