containerd/containerd · error

seek on closed reader: %w

Error message

seek on closed reader: %w

What it means

The closeOnEOFReadSeeker wrapper refuses Seek calls after its underlying reader has been closed, returning this error wrapping io.ErrClosedPipe. It exists to prevent seeking on a reader that was already shut down (the wrapper closes itself on EOF).

Source

Thrown at core/remotes/handlers.go:466

}

func (c *closeOnEOFReader) Close() error {
	var err error
	c.once.Do(func() {
		c.closed = true
		err = c.rc.Close()
	})
	return err
}

type closeOnEOFReadSeeker struct {
	closeOnEOFReader
	seeker io.Seeker
}

func (c *closeOnEOFReadSeeker) Seek(offset int64, whence int) (int64, error) {
	if c.closed {
		return 0, fmt.Errorf("seek on closed reader: %w", io.ErrClosedPipe)
	}
	return c.seeker.Seek(offset, whence)
}

View on GitHub (pinned to 4246446a2b)

Solutions

  1. Don't reuse or re-seek the reader after it reached EOF/closed; create a new fetch
  2. If re-reading is needed, re-open the content (re-fetch the blob) instead of seeking back
  3. Check caller logic for double verification passes on the same reader

Example fix

// before: reusing closed reader
_, err = rs.Seek(0, io.SeekStart) // seek on closed reader
// after: re-fetch instead
resp, err := fetcher.Fetch(ctx, desc)
rs := newCloseOnEOFReadSeeker(resp.Body)
Defensive patterns

Strategy: type-guard

Validate before calling

// before seeking, confirm reader not closed
// track close state yourself

Type guard

func seekable(rs io.ReadSeeker) bool {
    type closedCheck interface{ Closed() bool }
    if c, ok := rs.(closedCheck); ok {
        return !c.Closed()
    }
    return true
}

Try / catch

if _, err := rs.Seek(0, io.SeekStart); err != nil {
    if errors.Is(err, io.ErrClosedPipe) {
        // re-fetch content instead of reusing the closed reader
        resp, err := fetcher.Fetch(ctx, desc)
        if err != nil { return err }
        rs = newCloseOnEOFReadSeeker(resp.Body)
    }
}

Prevention

When it happens

Trigger: Calling Seek on a closeOnEOFReadSeeker after Close was invoked — typically when content verification code re-seeks a reader that already hit EOF and was closed (e.g. re-reading a response body for a second digest verification pass).

Common situations: Re-verifying fetched content twice; retry logic seeking the same reader after consumption; libraries expecting an open io.ReadSeeker but receiving one closed by the handler.

Related errors


AI-assisted analysis of containerd/containerd@4246446a2b (2026-09-02). Data as JSON: /api/errors/5aec769af3f4e3b4. Report an issue: GitHub.