nats-io/nats-server · error

ErrInvalidArchive

ErrInvalidArchive

Error message

archive: invalid archive stream

What it means

ErrInvalidArchive means the archive stream is corrupt, truncated, or not a NATS archive at all. The Reader throws it when the magic bytes don't match NATSARC1, when a header field hits EOF mid-entry (truncation), when a varint is malformed, when declared sizes overflow or exceed maxNameLen, when a payload read comes up short, or when discarding the current entry hits EOF early. It is sticky: once set on Reader.err, all subsequent Next/Read calls fail with it.

Source

Thrown at server/archive/archive.go:32

package archive

import (
	"bufio"
	"encoding/binary"
	"errors"
	"io"
)

const MagicBytes = "NATSARC1"

// maxNameLen bounds the entry name length accepted when reading an archive,
// avoiding an unbounded allocation on corrupt or malicious input. The name is
// the only variable-length field read directly from the stream.
const maxNameLen = 1 << 20

var (
	ErrClosed            = errors.New("archive: closed")
	ErrInvalidArchive    = errors.New("archive: invalid archive stream")
	ErrIncompleteEntry   = errors.New("archive: entry not fully written")
	ErrNoActiveEntry     = errors.New("archive: no active entry")
	ErrWriteTooLong      = errors.New("archive: write exceeds declared entry size")
	ErrNilHeader         = errors.New("archive: nil header")
	ErrNegativeEntrySize = errors.New("archive: negative entry size")
)

// Header describes one archive entry.
//
// On the wire each entry is the encoded header fields followed by the payload.
// HeaderSize and PayloadSize describe how that payload is split (e.g. message
// headers vs. body); the payload length is their sum and is not stored
// separately. Sequence is always encoded, with 0 meaning "unset".
type Header struct {
	Name        string
	HeaderSize  int64
	PayloadSize int64
	Timestamp   int64

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Verify the input is a complete NATSARC1 archive: check the first 8 bytes and file completeness before opening a Reader.
  2. Re-acquire or re-export the archive from the source; a truncated archive cannot be repaired in place — recover entries up to the corruption point by treating ErrInvalidArchive as end-of-valid-data.
  3. Check the writer side: ensure the producer called WriteHeader and wrote exactly HeaderSize+PayloadSize bytes per entry, then Close/Flush'd the stream.
  4. Confirm the archive format version matches this library (MagicBytes "NATSARC1"); regenerate with a compatible writer.
  5. If reading from a network stream, ensure the connection delivered the full bytes (no proxy truncation); retry with a fresh full copy.

Example fix

// before: reading an unknown file blindly
r := archive.NewReader(f)
for {
  h, err := r.Next()
  if err != nil { log.Fatal(err) } // dies on ErrInvalidArchive mid-loop
}
// after: detect and treat corruption as end-of-valid-data
r := archive.NewReader(f)
for {
  h, err := r.Next()
  if errors.Is(err, archive.ErrInvalidArchive) {
    log.Printf("archive corrupt/truncated after entry %d", i)
    break
  }
  if err != nil { log.Fatal(err) }
  i++
}
Defensive patterns

Strategy: type-guard

Validate before calling

// before reading
info, err := f.Stat()
if err != nil { return err }
if info.Size() < len(archive.MagicBytes) { return errors.New("file too small to be a NATS archive") }
magic := make([]byte, len(archive.MagicBytes))
if _, err := io.ReadFull(f, magic); err != nil { return err }
if string(magic) != archive.MagicBytes { return errors.New("not a NATSARC1 archive") }
f.Seek(0, io.SeekStart)

Type guard

func IsInvalidArchive(err error) bool { return errors.Is(err, archive.ErrInvalidArchive) }

Try / catch

h, err := r.Next()
switch {
case errors.Is(err, io.EOF):
    return nil // clean end of stream
case errors.Is(err, archive.ErrInvalidArchive):
    return fmt.Errorf("archive corrupt after entry %d: %w", count, err) // stop; state is poisoned
case err != nil:
    return err
}

Prevention

When it happens

Trigger: Reader.Next: magic mismatch (non-archive or wrong-version input); clean EOF on any header field after nameLen (truncated entry header); nameLen > maxNameLen (1 MiB); hdrSize/plSize negative after int64 conversion or total overflow; io.ReadFull short read of the entry name. Reader.Read: io.ReadFull returns any error mid-payload. Reader.discardCurrent (called via Next): EOF while skipping remaining payload bytes.

Common situations: Reading a file that isn't a NATS archive (wrong file passed, empty or zero-byte file, text/JSON logs); a truncated download or partial flush from a crashed producer; an archive written by a different/older format version; feeding the reader a compressed or re-encoded stream.

Related errors


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/5158c23a5eb81e53. Report an issue: GitHub.