golang/go · critical
zip: SetOffset called after data was written
Error message
zip: SetOffset called after data was written
What it means
`zip.Writer.SetOffset(n)` records where the zip archive begins in the underlying stream, so that offsets in the central directory are correct when the zip is appended to a preamble (e.g. a self-extracting executable). It panics if any data has already been written to the writer, because adjusting the offset after bytes exist would corrupt those offsets. The check is `w.cw.count != 0`.
Source
Thrown at src/archive/zip/writer.go:59
raw bool
}
// NewWriter returns a new [Writer] writing a zip file to w.
//
// Note that the exact bytes written to w are not covered by the Go 1
// compatibility promise. Callers, including tests, should not depend on the
// exact written bytes.
func NewWriter(w io.Writer) *Writer {
return &Writer{cw: &countWriter{w: bufio.NewWriter(w)}}
}
// SetOffset sets the offset of the beginning of the zip data within the
// underlying writer. It should be used when the zip data is appended to an
// existing file, such as a binary executable.
// It must be called before any data is written.
func (w *Writer) SetOffset(n int64) {
if w.cw.count != 0 {
panic("zip: SetOffset called after data was written")
}
w.cw.count = n
}
// Flush flushes any buffered data to the underlying writer.
// Calling Flush is not normally necessary; calling Close is sufficient.
func (w *Writer) Flush() error {
return w.cw.w.(*bufio.Writer).Flush()
}
// SetComment sets the end-of-central-directory comment field.
// It can only be called before [Writer.Close].
func (w *Writer) SetComment(comment string) error {
if len(comment) > uint16max {
return errors.New("zip: Writer.Comment too long")
}
w.comment = comment
return nilView on GitHub (pinned to b6b368adc5)
Solutions
- Call `SetOffset(n)` immediately after `zip.NewWriter(w)` and before any `Create`/`Write`.
- If you wrote a preamble to the same underlying writer, pass its exact length as the offset.
- If you need to change the offset, discard the writer and create a fresh one.
Example fix
// before
zw := zip.NewWriter(w)
zw.Create("a.txt") // writes header
zw.SetOffset(preambleLen) // panic
// after
zw := zip.NewWriter(w)
zw.SetOffset(preambleLen)
zw.Create("a.txt") Defensive patterns
Strategy: validation
Validate before calling
zw := zip.NewWriter(w) zw.SetOffset(preambleLen) // must precede any Create/Write
Prevention
- Call SetOffset immediately after NewWriter when appending to a preamble.
- Treat SetOffset as part of construction, not as a runtime setter.
When it happens
Trigger: Calling `Create`/`Write`/`Flush` before `SetOffset`; calling `SetOffset` after appending a file to the zip; reusing a Writer that already has buffered output.
Common situations: Self-extracting-executable builders that write the binary preamble first, then open a zip Writer but forget to `SetOffset(len(preamble))` before adding entries — or remember, but call it too late after a probe Write; refactors that reorder setup.
Related errors
- decompressor already registered
- compressor already registered
- invalid file name: %v
- missing top-level directory prefix
- zip file contains more than one top-level directory
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/6a6a194dc65c7574.
Report an issue: GitHub.