FiloSottile/age · error

stream.Writer is already closed

Error message

stream.Writer is already closed

What it means

Calling Close on a stream.Writer a second time returns this error (stored in w.err on first Close). After Close, the writer's internal error is poisoned so any further Write or Close fails. Note the first Close returns nil while setting this error for subsequent calls.

Source

Thrown at internal/stream/stream.go:236

				return 0, err
			}
		}
	}
	return total, nil
}

// Close flushes the last chunk. It does not close the underlying Writer.
func (w *EncryptWriter) Close() error {
	if w.err != nil {
		return w.err
	}

	w.err = w.flushChunk(lastChunk)
	if w.err != nil {
		return w.err
	}

	w.err = errors.New("stream.Writer is already closed")
	return nil
}

const (
	lastChunk    = true
	notLastChunk = false
)

func (w *EncryptWriter) flushChunk(last bool) error {
	if !last && w.buf.Len() != ChunkSize {
		panic("stream: internal error: flush called with partial chunk")
	}

	if last {
		setLastChunkFlag(&w.nonce)
	}
	w.buf.Grow(chacha20poly1305.Overhead)
	ciphertext := w.a.Seal(w.buf.Bytes()[:0], w.nonce[:], w.buf.Bytes(), nil)

View on GitHub (pinned to b74dce4cdb)

Solutions

  1. Ensure Close is called exactly once, e.g. with a sync.Once or a bool closed flag.
  2. If using defer w.Close(), do not also call Close on the success path.
  3. Ignore the error if idempotent close semantics are desired: check err.Error() for "already closed" and treat as success.

Example fix

// before
defer w.Close()
...
if err := w.Close(); err != nil { ... } // second close
// after
var closeOnce sync.Once
closeFn := func() error {
    var err error
    closeOnce.Do(func() { err = w.Close() })
    return err
}
Defensive patterns

Strategy: try-catch

Try / catch

var closeOnce sync.Once
close := func() error {
    var err error
    closeOnce.Do(func() { err = w.Close() })
    return err
}
if err := close(); err != nil && strings.Contains(err.Error(), "already closed") {
    err = nil
}

Prevention

When it happens

Trigger: Calling Close twice on the same *stream.Writer — e.g. explicit Close plus deferred Close, or Close inside both a flush routine and a defer.

Common situations: double-close via defer and explicit error-handling path; wrapping the writer in another closer that also closes it; reusing a writer object after close.

Related errors


AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31). Data as JSON: /api/errors/78451d0c60ade912. Report an issue: GitHub.