nats-io/nats-server · error

failed to read existing metadata header: %w

Error message

failed to read existing metadata header: %w

What it means

Returned during block recompression when UnmarshalMetadata cannot parse the compression metadata header at the end of the existing block file. A missing header is fine (defaults to no compression); this error means a header exists but is corrupt/unparseable, so the recompression cannot proceed safely.

Source

Thrown at server/filestore.go:7985

	if err != nil {
		return fmt.Errorf("failed to read original block from disk: %w", err)
	}

	// If the block is encrypted then we will need to decrypt it before
	// doing anything. We always encrypt after compressing because then the
	// compression can be as efficient as possible on the raw data, whereas
	// the encrypted ciphertext will not compress anywhere near as well.
	// The block encryption also covers the optional compression metadata.
	if err = mb.encryptOrDecryptIfNeeded(origBuf); err != nil {
		return err
	}

	meta := &CompressionInfo{}
	if _, err := meta.UnmarshalMetadata(origBuf); err != nil {
		// An error is only returned here if there's a problem with parsing
		// the metadata. If the file has no metadata at all, no error is
		// returned and the algorithm defaults to no compression.
		return fmt.Errorf("failed to read existing metadata header: %w", err)
	}
	if meta.Algorithm == alg {
		// The block is already compressed with the chosen algorithm so there
		// is nothing else to do. This is not a common case, it is here only
		// to ensure we don't do unnecessary work in case something asked us
		// to recompress an already compressed block with the same algorithm.
		return nil
	} else if alg != NoCompression {
		// The block is already compressed using some algorithm, so we need
		// to decompress the block using the existing algorithm before we can
		// recompress it with the new one.
		if origBuf, err = meta.Algorithm.Decompress(origBuf); err != nil {
			return fmt.Errorf("failed to decompress original block: %w", err)
		}
	}

	return mb.atomicOverwriteFile(origBuf, true)
}

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Inspect the wrapped cause; compare the NATS version that wrote the block vs the running version
  2. If the block is expendable, let JetStream rebuild/remove it or re-create the stream and repopulate
  3. Restore the block file from backup and retry recompression
  4. Upgrade nats-server to the latest patch release where metadata parsing is more tolerant

Example fix

// version mismatch scenario
nats-server -v  # running 2.9.x
// block written by 2.10.x — upgrade before recompressing
systemctl upgrade nats-server && systemctl restart nats-server
Defensive patterns

Strategy: validation

Validate before calling

// Go: don't recompress blocks written by an incompatible server version
if runningVersion.Major() != writtenVersion.Major() {
    return fmt.Errorf("align nats-server version (%s) with datadir writer (%s) before recompress",
        runningVersion, writtenVersion)
}

Try / catch

if err != nil && strings.Contains(err.Error(), "failed to read existing metadata header") {
    // corrupt metadata: restore block from backup or recreate stream
    return restoreBlockFromBackup(blockPath)
}

Prevention

When it happens

Trigger: RecompressMsgBlock on a block whose trailing metadata header bytes are corrupt — partial write from an unclean shutdown, bit rot, or an on-disk format written by a newer/older incompatible NATS version.

Common situations: Upgrading/downgrading across NATS versions with changed compression metadata layout; disk corruption; manually truncating or editing block files.

Related errors


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