hashicorp/nomad · error

failed to decompress snapshot: %v

Error message

failed to decompress snapshot: %v

What it means

CopySnapshot wraps gzip.NewReader failures when opening the incoming snapshot stream for decompression. The stream is not valid gzip data from the very first bytes, so the metadata cannot be read and the copy aborts. Raised when callers stream snapshots in (e.g. from an HTTP restore endpoint) and Verify validates them.

Source

Thrown at helper/snapshot/snapshot.go:205

	io.Writer
}

func (dc Discard) Close() error { return nil }

// Verify takes the snapshot from the reader and verifies its contents.
func Verify(in io.Reader) (*raft.SnapshotMeta, error) {
	return CopySnapshot(in, Discard{Writer: io.Discard})
}

// CopySnapshot copies the snapshot content from snapshot archive to dest.
// It will close the destination once complete.
func CopySnapshot(in io.Reader, dest io.WriteCloser) (*raft.SnapshotMeta, error) {
	defer dest.Close()

	// Wrap the reader in a gzip decompressor.
	decomp, err := gzip.NewReader(in)
	if err != nil {
		return nil, fmt.Errorf("failed to decompress snapshot: %v", err)
	}
	defer decomp.Close()

	// Read the archive, throwing away the snapshot data.
	var metadata raft.SnapshotMeta
	if err := read(decomp, &metadata, dest); err != nil {
		return nil, fmt.Errorf("failed to read snapshot file: %v", err)
	}

	if err := concludeGzipRead(decomp); err != nil {
		return nil, err
	}

	return &metadata, nil
}

// concludeGzipRead should be invoked after you think you've consumed all of
// the data from the gzip stream. It will error if the stream was corrupt.

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Inspect the first bytes of the input — valid gzip starts with 1f 8b; if you see '<html>' or JSON, the transport returned an error page, fix auth/proxy/URL.
  2. Re-download or re-create the snapshot; the source file is truncated or corrupt.
  3. Verify the client code streams the raw response body (no intermediate transformation/decoding of the body).
  4. Use the snapshot's checksum header to validate integrity before passing the stream to CopySnapshot.

Example fix

// before
resp, _ := http.Get(url)
meta, err := CopySnapshot(resp.Body, dest) // body may be an HTML error page
// after
if resp.StatusCode != http.StatusOK { return fmt.Errorf("snapshot fetch failed: %s", resp.Status) }
meta, err := CopySnapshot(resp.Body, dest)
Defensive patterns

Strategy: validation

Validate before calling

func isGzip(r io.Reader) (bool, error) {
	magic := make([]byte, 2)
	if _, err := io.ReadFull(r, magic); err != nil { return false, err }
	return magic[0] == 0x1f && magic[1] == 0x8b, nil
}

Try / catch

meta, err := CopySnapshot(in, dest)
if err != nil && strings.Contains(err.Error(), "failed to decompress snapshot") {
	return fmt.Errorf("input is not a gzip snapshot stream: %w", err)
}

Prevention

When it happens

Trigger: Calling CopySnapshot (directly or via Verify) with a reader whose first bytes are not a valid gzip header: truncated/empty response, HTML error page from a proxy, wrong endpoint, or data that was stored/downloaded without its gzip envelope.

Common situations: Reverse proxy or LB returning a 403/502 HTML page instead of the snapshot bytes; snapshot URL saved to disk and replayed with extra bytes; interrupted download truncating the gzip stream; TLS/auth problems yielding an error page body.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/268f88e5b56cb230. Report an issue: GitHub.