golang/go · error

ErrWriteAfterClose

ErrWriteAfterClose

Error message

archive/tar: write after close

What it means

ErrWriteAfterClose is returned by Write, WriteHeader, Flush, and ReadFrom after Close has been called on the Writer. Close sets an internal error sentinel (tw.err = ErrWriteAfterClose) that all subsequent operations check, so any post-close use is rejected. Close itself is idempotent (returns nil if already closed).

Source

Thrown at src/archive/tar/common.go:37

	"math"
	"path"
	"reflect"
	"strconv"
	"strings"
	"time"
)

// BUG: Use of the Uid and Gid fields in Header could overflow on 32-bit
// architectures. If a large value is encountered when decoding, the result
// stored in Header will be the truncated version.

var tarinsecurepath = godebug.New("tarinsecurepath")

var (
	ErrHeader          = errors.New("archive/tar: invalid tar header")
	ErrWriteTooLong    = errors.New("archive/tar: write too long")
	ErrFieldTooLong    = errors.New("archive/tar: header field too long")
	ErrWriteAfterClose = errors.New("archive/tar: write after close")
	ErrInsecurePath    = errors.New("archive/tar: insecure file path")
	errMissData        = errors.New("archive/tar: sparse file references non-existent data")
	errUnrefData       = errors.New("archive/tar: sparse file contains unreferenced data")
	errWriteHole       = errors.New("archive/tar: write non-NUL byte in sparse hole")
	errSparseTooLong   = errors.New("archive/tar: sparse map too long")
)

type headerError []string

func (he headerError) Error() string {
	const prefix = "archive/tar: cannot encode header"
	var ss []string
	for _, s := range he {
		if s != "" {
			ss = append(ss, s)
		}
	}
	if len(ss) == 0 {

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Treat Close as terminal: ensure no Write/WriteHeader/Flush calls happen after it (use a sync.Once or a closed flag).
  2. Do not defer Close alongside concurrent writes; serialize close with a mutex or channel.
  3. In error paths, return immediately after Close instead of continuing the write loop.
  4. If pooling writers, always create a fresh tar.NewWriter; never reuse a closed one.

Example fix

// before
tw := tar.NewWriter(w)
tw.Close()
tw.Write([]byte("x")) // throws ErrWriteAfterClose

// after
tw := tar.NewWriter(w)
defer tw.Close()
// ... all writes happen here, before defer runs
Defensive patterns

Strategy: try-catch

Validate before calling

type safeWriter struct{ *tar.Writer; closed bool }
func (s *safeWriter) Close() error { s.closed = true; return s.Writer.Close() }
func (s *safeWriter) Write(b []byte) (int, error) {
  if s.closed { return 0, nil } // or return tar.ErrWriteAfterClose
  return s.Writer.Write(b)
}

Try / catch

_, err := tw.Write(b)
if errors.Is(err, tar.ErrWriteAfterClose) {
  return fmt.Errorf("logic error: write after close on %s", name)
}

Prevention

When it happens

Trigger: Calling tw.Write(...) or tw.WriteHeader(...) after tw.Close(); flushing or copying into the writer after finalizing the footer; reused Writer instance whose Close was triggered early by an error in a previous operation.

Common situations: Deferred Close racing with an explicit write in a goroutine; error handling that calls Close on a partial write then continues the loop; cleanup paths that close prematurely; misusing a pooled/tar writer that was returned to the pool closed.

Related errors


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/d69e7fdee15e5592. Report an issue: GitHub.