kubernetes/kops · error
error seeking to start of data stream for write to %s: %v
Error message
error seeking to start of data stream for write to %s: %v
What it means
GSPath.WriteFile takes an io.ReadSeeker and computes an MD5 hash up front; before each upload attempt (including retries) it seeks the stream back to the beginning with data.Seek(0, 0). If the seek fails — because the reader does not actually support seeking, or the underlying resource (pipe, socket, network stream, closed file) cannot be rewound — the write aborts with this message. It is not retried, since retrying cannot fix a non-seekable stream.
Source
Thrown at util/pkg/vfs/gsfs.go:197
if err != nil {
return err
}
done, err := RetryWithBackoff(gcsWriteBackoff, func() (bool, error) {
var objectACL []storage.ACLRule
if acl != nil {
gsACL, ok := acl.(*GSAcl)
if !ok {
return true, fmt.Errorf("write to %s with ACL of unexpected type %T", p, acl)
}
objectACL = gsACL.Acl
klog.V(4).Infof("Writing file %q with ACL %v", p, gsACL)
} else {
klog.V(4).Infof("Writing file %q", p)
}
if _, err := data.Seek(0, 0); err != nil {
return false, fmt.Errorf("error seeking to start of data stream for write to %s: %v", p, err)
}
client, err := p.getStorageClient(ctx)
if err != nil {
return false, err
}
w := client.Bucket(p.bucket).Object(p.key).NewWriter(ctx)
// The upload is rejected if the data does not match this MD5 hash
w.MD5 = md5Hash.HashValue
w.ACL = objectACL
if _, err := io.Copy(w, data); err != nil {
w.Close()
return false, fmt.Errorf("error writing %s: %v", p, err)
}
if err := w.Close(); err != nil {
return false, fmt.Errorf("error writing %s: %v", p, err)
}View on GitHub (pinned to 4c8573c808)
Solutions
- Buffer the content before writing: read the stream into memory (bytes.NewReader(b)) or a temp file (*os.File on a regular file) so Seek is supported.
- If already using a bytes.Reader that was partially consumed, call br.Reset(b) before WriteFile, or wrap fresh bytes.NewReader(data) at the call site.
- If using an *os.File, ensure it is a regular file (not a pipe/FIFO/stdin) and that it is still open when WriteFile is called; fix close ordering or defer the close until after WriteFile.
- As a last resort, upgrade the reader type in your API to *bytes.Reader / *os.File (concrete seekable types) so the compiler prevents passing non-seekable streams.
Example fix
// before resp, _ := http.Get(url) p.WriteFile(ctx, resp.Body, nil) // body is a non-seekable stream // after resp, _ := http.Get(url) b, _ := io.ReadAll(resp.Body) resp.Body.Close() p.WriteFile(ctx, bytes.NewReader(b), nil)
Defensive patterns
Strategy: validation
Validate before calling
// Verify the reader is truly seekable before calling WriteFile:
func requireSeekable(r io.Reader) error {
rs, ok := r.(io.ReadSeeker)
if !ok {
return fmt.Errorf("reader %T is not seekable", r)
}
if _, err := rs.Seek(0, io.SeekStart); err != nil {
return fmt.Errorf("reader %T cannot seek: %w", r, err)
}
_, err := rs.Seek(0, io.SeekStart) // rewind again for the caller
return err
} Type guard
func isSeekable(r io.Reader) bool {
switch r.(type) {
case *bytes.Reader, *bytes.Buffer, *strings.Reader, *os.File:
return true // os.File only for regular files; pipes/FIFOs will fail
default:
return false
}
} Try / catch
if err := gsPath.WriteFile(ctx, data, acl); err != nil {
if strings.Contains(err.Error(), "error seeking to start of data stream") {
// materialize the stream and retry once
b, rerr := io.ReadAll(data)
if rerr != nil { return rerr }
if werr := gsPath.WriteFile(ctx, bytes.NewReader(b), acl); werr != nil {
return werr
}
return nil
}
return err
} Prevention
- Never pass os.Stdin, pipes, network/HTTP response bodies, or gzip streams directly to WriteFile — buffer them first.
- Prefer concrete seekable types (*bytes.Reader, *os.File) in your write-path APIs instead of io.ReadSeeker.
- Keep the file open until after WriteFile returns; 'file already closed' surfaces as a seek failure.
- Remember Seek(0,0) runs on every retry attempt, so the reader must remain rewindable for the lifetime of the call.
When it happens
Trigger: Calling GSPath.WriteFile(ctx, data, acl) with an io.Reader that is not truly seekable despite satisfying io.ReadSeeker: os.Pipe / net.Conn wrappers, bytes.Reader whose offset was already consumed without Reset, an *os.File opened on a pipe/FIFO or character device, or a file that was closed before the (possibly retried) write attempt.
Common situations: Streaming data from stdin (os.Stdin) or an HTTP response body directly into WriteFile; passing a gzip/HTTP body reader; passing a file whose descriptor was closed earlier in the code path ('file already closed' seek error).
Related errors
- write to %s with ACL of unexpected type %T
- error reading stdin: %v
- encoding resources for %v: %w
- building VFS path for %q: %w
- reading expected user-data: %w
AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05).
Data as JSON: /api/errors/ea244d69c91e5da9.
Report an issue: GitHub.