nats-io/nats-server · error
ErrClosed
ErrClosed
Error message
archive: closed
What it means
The archive package (server/archive) defines ErrClosed = errors.New("archive: closed"). The Writer rejects all operations after Close has been called: WriteHeader (archive.go:79) and Write (archive.go:129) return ErrClosed when a.closed is set. It signals use of an already-finalized archive writer, mirroring the sentinel-error style of compress/gzip and archive/tar.
Source
Thrown at server/archive/archive.go:31
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 int64View on GitHub (pinned to 3a66a489d2)
Solutions
- Restructure code so all WriteHeader/Write calls happen before Close; Close must be the final operation on the Writer.
- If the writer may be closed early on error, guard subsequent writes (check a closed flag or skip the write path).
- Compare against archive.ErrClosed with errors.Is to handle this case explicitly instead of treating it as I/O failure.
- Create a new Writer if you need to write another archive after closing the previous one.
Example fix
// before
def w.Close()
w.WriteHeader(hdr) // may run after Close -> ErrClosed
// after
err := writeEntries(w, hdrs)
if err == nil {
err = w.Close()
}
if errors.Is(err, archive.ErrClosed) {
// writer already finalized; open a new one to continue
w = archive.NewWriter(sink)
} Defensive patterns
Strategy: try-catch
Validate before calling
// guard against writing to an already-closed writer
var mu sync.Mutex
closed := false
func safeWriteHeader(w *archive.Writer, hdr *archive.Header) error {
mu.Lock()
if closed { mu.Unlock(); return archive.ErrClosed }
mu.Unlock()
return w.WriteHeader(hdr)
} Type guard
func isClosed(err error) bool { return errors.Is(err, archive.ErrClosed) } Try / catch
if err := w.WriteHeader(hdr); err != nil {
if errors.Is(err, archive.ErrClosed) {
// writer finalized: open a fresh writer or abort the entry stream
return fmt.Errorf("archive writer already closed: %w", err)
}
return err
} Prevention
- Treat Close as the terminal operation: never call WriteHeader/Write/Flush after it.
- Use defer w.Close() only when no writes can occur after the deferred call (e.g. close in the owning function, not in callees).
- Compare with errors.Is(err, archive.ErrClosed) rather than string matching.
- If close-on-error paths exist, structure writes so the error path returns before attempting further entries.
When it happens
Trigger: Calling WriteHeader, Write, or Flush on an *archive.Writer after Close() has returned; closing the writer and then attempting to write another entry; double-close followed by reuse of the same Writer value.
Common situations: Code paths that close the archive on error (e.g. defer w.Close()) and then still attempt writes in a cleanup path; reusing a Writer field across retries after one attempt closed it; lifetime bugs where the writer is closed by one goroutine while another still streams entries.
Related errors
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/d36c354a1050f6e6.
Report an issue: GitHub.