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
- Ensure the stream/block is open (call ensureMsgBlockOpened or openMsgBlock) before truncating
- Check for concurrent stream close/delete racing the truncate; serialize operations on the stream
- If this appears during recovery, inspect disk state for corrupt block metadata and restart the server to re-open blocks
- 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
- Don't purge/truncate streams concurrently with stream delete/close
- Restart the server cleanly if recovery left blocks closed
- Upgrade nats-server to pick up block lifecycle fixes
- Monitor stream health with `nats stream info` before maintenance operations
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
- unknown compression algorithm
- name required
- fileStore requires file storage type in config
- filestore max block size is %s
- could not create hash: %v
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/7173bcf1bffc8873.
Report an issue: GitHub.