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
- Remove any trailing bytes after the gzip archive in the snapshot file/stream (the count in the message tells you how many).
- Check the producer: ensure nothing writes to the stream after writeSnapshot's compressor is closed.
- If multiple gzip members were concatenated, split and use only the single intended snapshot archive.
- 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
- Never concatenate or append to snapshot files with external tooling.
- Write the HTTP response body exactly once when saving snapshots.
- Use checksum validation to detect any post-save modification.
- Keep producer/consumer snapshot formats in version lockstep.
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
- failed to read or write snapshot data: %v
- failed to compress snapshot file: %v
- failed to decompress snapshot: %v
- failed to read after %v: %v
- volume snapshot ID cannot be updated
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/f9eae86fae0edfea.
Report an issue: GitHub.