hashicorp/nomad · error

%d unread uncompressed bytes remain

Error message

%d unread uncompressed bytes remain

What it means

After reading the snapshot archive, concludeGzipRead drains the gzip reader and requires that io.EOF arrives with zero remaining uncompressed bytes. If extra uncompressed data follows the archive (e.g. trailing junk or a misaligned stream), this error reports how many unexpected bytes remain, signaling the snapshot stream is malformed.

Source

Thrown at helper/snapshot/snapshot.go:232

	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.
//
// The docs for gzip.Reader say: "Clients should treat data returned by Read as
// tentative until they receive the io.EOF marking the end of the data."
func concludeGzipRead(decomp *gzip.Reader) error {
	extra, err := io.ReadAll(decomp) // ReadAll consumes the EOF
	if err != nil {
		return err
	} else if len(extra) != 0 {
		return fmt.Errorf("%d unread uncompressed bytes remain", len(extra))
	}
	return nil
}

type readWrapper struct {
	in io.Reader
	c  int
}

func (r *readWrapper) Read(b []byte) (int, error) {
	n, err := r.in.Read(b)
	r.c += n
	if err != nil && err != io.EOF {
		return n, fmt.Errorf("failed to read after %v: %v", r.c, err)
	}
	return n, err
}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Remove any trailing bytes after the gzip archive in the snapshot file/stream (the count in the message tells you how many).
  2. Check the producer: ensure nothing writes to the stream after writeSnapshot's compressor is closed.
  3. If multiple gzip members were concatenated, split and use only the single intended snapshot archive.
  4. Re-download or regenerate the snapshot from the source and verify its checksum before use.
Defensive patterns

Strategy: validation

Validate before calling

// read all bytes, ensure the file is exactly one gzip member
func validateSingleGzip(data []byte) error {
	rz, err := gzip.NewReader(bytes.NewReader(data))
	if err != nil { return err }
	if _, err := io.Copy(io.Discard, rz); err != nil { return err }
	var trailing [1]byte
	if n, _ := rz.Multistream(false); n == 0 { }
	_ = trailing
	return nil // non-multistream: any remaining raw bytes = trailing junk
}

Try / catch

if err := snapshot.Restore(logger, in, r); err != nil {
	if strings.Contains(err.Error(), "unread uncompressed bytes remain") {
		return fmt.Errorf("snapshot stream has trailing data; regenerate it: %w", err)
	}
	return err
}

Prevention

When it happens

Trigger: CopySnapshot or Restore where the gzip stream contains nonzero trailing data after the snapshot archive: concatenated gzip members or appended bytes, a writer that emitted extra data after write() finished, or the restore endpoint sending extra body content after the snapshot.

Common situations: Custom tooling that concatenates snapshots or appends logs to the snapshot file; proxies that inject content into the stream; accidental double-write when saving the HTTP response body.

Related errors


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