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
- Ensure Close is called exactly once, e.g. with a sync.Once or a bool closed flag.
- If using defer w.Close(), do not also call Close on the success path.
- 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
- Close stream.Writer exactly once — prefer a single defer without a duplicate explicit Close.
- Never reuse a closed writer; create a new one for each stream.
- Remember the first Close returns nil but poisons the writer.
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
- ArmoredWriter already closed
- trailing data after end of encrypted file
- last chunk is empty, try age v1.0.0, and please consider rep
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/78451d0c60ade912.
Report an issue: GitHub.