nats-io/nats-server · error

failed to truncate msg block %d, file not open

Error message

failed to truncate msg block %d, file not open

What it means

Returned by FileStore msg block truncation when the block's message file descriptor (mb.mfd) is not open. Truncation requires reading a trailing 8-byte length check value with ReadAt, which is impossible on a closed/nil file handle. The library guards against operating on blocks whose backing file has been closed or never opened.

Source

Thrown at server/filestore.go:6907

		// and would otherwise have compressed data and allow to write uncompressed data in the same block.
		if err = mb.atomicOverwriteFile(buf, false); err != nil {
			return 0, 0, err
		}
	} else if mb.mfd != nil {
		if err = mb.mfd.Truncate(eof); err != nil {
			return 0, 0, err
		}
		if err = mb.mfd.Sync(); err != nil {
			return 0, 0, err
		}
		// Update our checksum.
		var lchk [8]byte
		if _, err = mb.mfd.ReadAt(lchk[:], eof-8); err != nil {
			return 0, 0, err
		}
		copy(mb.lchk[0:], lchk[:])
	} else {
		return 0, 0, fmt.Errorf("failed to truncate msg block %d, file not open", mb.index)
	}

	// Update our last msg.
	atomic.StoreUint64(&mb.last.seq, tseq)
	mb.last.ts = ts

	// Clear our cache.
	mb.clearCacheAndOffset()

	// Redo per subject info for this block.
	if err = mb.resetPerSubjectInfo(); err != nil {
		return purged, bytes, err
	}

	// Load msgs again.
	return purged, bytes, mb.loadMsgsWithLock()
}

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Ensure the stream/block is open (call ensureMsgBlockOpened or openMsgBlock) before truncating
  2. Check for concurrent stream close/delete racing the truncate; serialize operations on the stream
  3. If this appears during recovery, inspect disk state for corrupt block metadata and restart the server to re-open blocks
  4. Update to a recent NATS server version; block lifecycle around truncation has had fixes

Example fix

// before: truncating a block that was closed
blk.truncate()

// after: ensure file is open first
if err := fs.ensureMsgBlockOpened(blk); err != nil { return err }
blk.truncate()
Defensive patterns

Strategy: try-catch

Validate before calling

// Go: ensure the stream is healthy before truncating
info, err := js.StreamInfo(ctx, "ORDERS")
if err != nil || info.State.Msgs == 0 {
    return fmt.Errorf("stream unavailable, skip truncate: %w", err)
}

Try / catch

err := stream.Purge(ctx, nats.PurgeSequence(seq))
var serr *nats.APIError
if errors.As(err, &serr) && strings.Contains(serr.Description, "file not open") {
    // restart server / reopen stream, then retry once
}

Prevention

When it happens

Trigger: Calling truncateMsgBlock (via stream purge/truncate APIs) on a message block whose mfd is nil — e.g. after the block was closed during recovery, or truncation attempted while the block file is not open because the block was compacted/closed earlier.

Common situations: Operating on a recovered stream where a block was closed during a prior failure; concurrent close of a stream while a truncate is in flight; calling internal-style purge APIs on blocks outside the expected open lifecycle.

Related errors


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