juicedata/juicefs · error
capture read position %s: %w
Error message
capture read position %s: %w
What it means
storjClient.Put first checks whether the input is an io.ReadSeeker so it can retry uploads by rewinding. Capturing the current position with rs.Seek(0, io.SeekCurrent) failing means the reader advertised seekability but the underlying Seek operation failed, so a resumable/retried upload cannot be safely started.
Source
Thrown at pkg/object/storj.go:151
download, e = s.project.DownloadObject(ctx, s.bucket, key, opts)
return e
})
if err != nil {
return nil, err
}
return download, nil
}
func (s *storjClient) Put(ctx context.Context, key string, in io.Reader, getters ...AttrGetter) error {
// Retry is only safe when the reader is seekable: rewinding lets us restart
// the upload from the beginning after a rate-limit response.
rs, seekable := in.(io.ReadSeeker)
if !seekable {
return s.putOnce(ctx, key, in)
}
startPos, err := rs.Seek(0, io.SeekCurrent)
if err != nil {
return fmt.Errorf("capture read position %s: %w", key, err)
}
return storjBackoff(ctx, func() error {
if _, err := rs.Seek(startPos, io.SeekStart); err != nil {
return err
}
return s.putOnce(ctx, key, rs)
})
}
func (s *storjClient) putOnce(ctx context.Context, key string, in io.Reader) error {
upload, err := s.project.UploadObject(ctx, s.bucket, key, nil)
if err != nil {
return fmt.Errorf("begin upload %s: %w", key, err)
}
if _, err = io.Copy(upload, in); err != nil {
_ = upload.Abort()
return fmt.Errorf("upload %s: %w", key, err)
}View on GitHub (pinned to c9a67b23e8)
Solutions
- Supply a fully seekable source (real file or bytes.Reader) instead of a pipe/stdin.
- If the input is not seekable, ensure it reaches the path that uses putOnce (single-shot upload) — don't wrap it in a fake ReadSeeker.
- If using a custom reader, implement Seek correctly or remove the io.ReadSeeker interface so Put falls back to putOnce.
Example fix
// before
in := bufio.NewReader(os.Stdin) // not truly seekable
client.Put(ctx, key, in)
// after
in, err := os.Open(tmpFile)
if err != nil { return err }
defer in.Close()
client.Put(ctx, key, in) // real io.ReadSeeker Defensive patterns
Strategy: type-guard
Validate before calling
if rs, ok := in.(io.ReadSeeker); ok {
if _, err := rs.Seek(0, io.SeekCurrent); err != nil {
return fmt.Errorf("input not truly seekable: %v", err)
}
} Type guard
func isSeekable(r io.Reader) bool {
rs, ok := r.(io.ReadSeeker)
if !ok { return false }
_, err := rs.Seek(0, io.SeekCurrent)
return err == nil
} Try / catch
if err := client.Put(ctx, key, in); err != nil && strings.Contains(err.Error(), "capture read position") {
// fall back to staging the data into a temp file, then retry Put
} Prevention
- Feed Put real files or in-memory buffers, not pipes/stdin.
- Don't wrap non-seekable readers in types that claim io.ReadSeeker.
- Test upload paths with piped input in CI to catch false seekability.
When it happens
Trigger: Passing a reader that implements io.ReadSeeker but whose Seek returns an error — e.g. an *os.File on a pipe/non-seekable fd, a closed file, or a custom reader whose Seek is unimplemented/fails at runtime, then calling Put.
Common situations: Piping from stdin or a process substitution into a JuiceFS command whose upload path expects seekable input; a wrapped reader (bufio/compression) that falsely claims ReadSeeker support.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- begin upload %s: %w
- Unable to skip %s bytes (position=%s, fileSize=%s): %s
- write
- ceph: can't put empty file
- closed
AI-assisted analysis of juicedata/juicefs@c9a67b23e8 (2026-09-06).
Data as JSON: /api/errors/d40fa3907205b4ee.
Report an issue: GitHub.