{"record":{"id":"d36c354a1050f6e6","repo":"nats-io/nats-server","slug":"errclosed","errorCode":"ErrClosed","errorMessage":"archive: closed","messagePattern":"archive: closed","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/archive/archive.go","lineNumber":31,"sourceCode":"\npackage archive\n\nimport (\n\t\"bufio\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"io\"\n)\n\nconst MagicBytes = \"NATSARC1\"\n\n// maxNameLen bounds the entry name length accepted when reading an archive,\n// avoiding an unbounded allocation on corrupt or malicious input. The name is\n// the only variable-length field read directly from the stream.\nconst maxNameLen = 1 << 20\n\nvar (\n\tErrClosed            = errors.New(\"archive: closed\")\n\tErrInvalidArchive    = errors.New(\"archive: invalid archive stream\")\n\tErrIncompleteEntry   = errors.New(\"archive: entry not fully written\")\n\tErrNoActiveEntry     = errors.New(\"archive: no active entry\")\n\tErrWriteTooLong      = errors.New(\"archive: write exceeds declared entry size\")\n\tErrNilHeader         = errors.New(\"archive: nil header\")\n\tErrNegativeEntrySize = errors.New(\"archive: negative entry size\")\n)\n\n// Header describes one archive entry.\n//\n// On the wire each entry is the encoded header fields followed by the payload.\n// HeaderSize and PayloadSize describe how that payload is split (e.g. message\n// headers vs. body); the payload length is their sum and is not stored\n// separately. Sequence is always encoded, with 0 meaning \"unset\".\ntype Header struct {\n\tName        string\n\tHeaderSize  int64\n\tPayloadSize int64","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/archive/archive.go#L13-L49","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\ndef w.Close()\nw.WriteHeader(hdr) // may run after Close -> ErrClosed\n// after\nerr := writeEntries(w, hdrs)\nif err == nil {\n    err = w.Close()\n}\nif errors.Is(err, archive.ErrClosed) {\n    // writer already finalized; open a new one to continue\n    w = archive.NewWriter(sink)\n}","handlingStrategy":"try-catch","validationCode":"// guard against writing to an already-closed writer\nvar mu sync.Mutex\nclosed := false\n\nfunc safeWriteHeader(w *archive.Writer, hdr *archive.Header) error {\n    mu.Lock()\n    if closed { mu.Unlock(); return archive.ErrClosed }\n    mu.Unlock()\n    return w.WriteHeader(hdr)\n}","typeGuard":"func isClosed(err error) bool { return errors.Is(err, archive.ErrClosed) }","tryCatchPattern":"if err := w.WriteHeader(hdr); err != nil {\n    if errors.Is(err, archive.ErrClosed) {\n        // writer finalized: open a fresh writer or abort the entry stream\n        return fmt.Errorf(\"archive writer already closed: %w\", err)\n    }\n    return err\n}","preventionTips":["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."],"tags":["go","archive","io","sentinel-error"],"backgroundTag":"use-after-close","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}