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 nil

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Call `SetOffset(n)` immediately after `zip.NewWriter(w)` and before any `Create`/`Write`.
  2. If you wrote a preamble to the same underlying writer, pass its exact length as the offset.
  3. 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

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


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