nats-io/nats-server · error

expected state.json contents

Error message

expected state.json contents

What it means

After confirming the first archive entry is named 'state.json', RestoreStreamV2 reads its full contents with io.ReadAll. If that read fails (underlying s2/tar reader error, e.g. corrupt compressed data or truncated stream), the restore is aborted with this message. Note the error text is a static string; the underlying read error is not wrapped.

Source

Thrown at server/stream_backup.go:264

// RestoreStreamSnapshotV2 will restore a stream from a snapshot.
func (a *Account) RestoreStreamV2(ncfg *StreamConfig, r io.Reader) (retMset *stream, retErr error) {
	dec := s2.NewReader(r)
	tr := archive.NewReader(dec)

	var nstate StreamState

	// Load the stream state.
	hdr, err := tr.Next()
	if err != nil {
		return nil, err
	}
	if hdr.Name != "state.json" {
		return nil, fmt.Errorf("expected state.json first")
	}
	state, err := io.ReadAll(tr)
	if err != nil {
		return nil, fmt.Errorf("expected state.json contents")
	}
	if err := json.Unmarshal(state, &nstate); err != nil {
		return nil, fmt.Errorf("error in state.json: %w", err)
	}

	s, jsa, err := a.checkForJetStream()
	if err != nil {
		return nil, err
	}
	js := jsa.js
	if js == nil {
		return nil, NewJSNotEnabledForAccountError()
	}
	if _, err := a.lookupStream(ncfg.Name); err == nil {
		return nil, NewJSStreamNameExistRestoreFailedError()
	}

	cfg, apiErr := s.checkStreamCfg(ncfg, a, false)

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Re-take the stream backup and verify it completes (snapshot ends with the end-of-backup sentinel)
  2. Check the snapshot file size/checksum against the source to detect truncation before restoring
  3. Restore over a stable connection; for large streams prefer server-side copy or file-based transport
  4. If the file is intact, inspect server logs for the underlying s2/tar read error (I/O, disk) and fix the storage/transport issue
Defensive patterns

Strategy: validation

Validate before calling

fi, err := os.Stat(snapshotPath)
if err != nil || fi.Size() == 0 {
    return fmt.Errorf("snapshot missing or empty: %s", snapshotPath)
}
if want, got := expectedChecksum, fileChecksum(snapshotPath); want != got {
    return fmt.Errorf("snapshot truncated: got %d bytes", got)
}

Try / catch

mset, err := acc.RestoreStreamV2(cfg, r)
if err != nil {
    if strings.Contains(err.Error(), "expected state.json contents") {
        // archive read failed mid-entry: treat snapshot as corrupt, re-transfer
    }
    return err
}

Prevention

When it happens

Trigger: The s2-compressed archive data for the state.json entry is truncated mid-read — a partially downloaded/copied snapshot, a backup interrupted before completion (missing the end-of-backup sentinel), or disk/network I/O failure while the server reads the request body during STREAM.RESTORE.

Common situations: Snapshot file cut off by a failed upload over the NATS connection; an s2 file corrupted in transit; a backup taken while the source server was shutting down; restoring over an unreliable network where the restore chunk stream breaks early.

Related errors


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