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
- Don't reuse or re-seek the reader after it reached EOF/closed; create a new fetch
- If re-reading is needed, re-open the content (re-fetch the blob) instead of seeking back
- 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
- Never reuse a reader after EOF-triggered close; re-fetch instead
- Avoid double-verification passes on the same fetched reader
- If re-reading is required, buffer the content in memory/disk first
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
- unable to discard to offset
- bufpipe: read/write on closed pipe
- `io_type` can only be `streaming` or `named_pipe`
- failed to get reader: %w
- failed to copy to tar: %w
AI-assisted analysis of containerd/containerd@4246446a2b (2026-09-02).
Data as JSON: /api/errors/5aec769af3f4e3b4.
Report an issue: GitHub.