{"record":{"id":"ea244d69c91e5da9","repo":"kubernetes/kops","slug":"error-seeking-to-start-of-data-stream-for-write-to","errorCode":null,"errorMessage":"error seeking to start of data stream for write to %s: %v","messagePattern":"error seeking to start of data stream for write to (.+?): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"util/pkg/vfs/gsfs.go","lineNumber":197,"sourceCode":"\tif err != nil {\n\t\treturn err\n\t}\n\n\tdone, err := RetryWithBackoff(gcsWriteBackoff, func() (bool, error) {\n\t\tvar objectACL []storage.ACLRule\n\t\tif acl != nil {\n\t\t\tgsACL, ok := acl.(*GSAcl)\n\t\t\tif !ok {\n\t\t\t\treturn true, fmt.Errorf(\"write to %s with ACL of unexpected type %T\", p, acl)\n\t\t\t}\n\t\t\tobjectACL = gsACL.Acl\n\t\t\tklog.V(4).Infof(\"Writing file %q with ACL %v\", p, gsACL)\n\t\t} else {\n\t\t\tklog.V(4).Infof(\"Writing file %q\", p)\n\t\t}\n\n\t\tif _, err := data.Seek(0, 0); err != nil {\n\t\t\treturn false, fmt.Errorf(\"error seeking to start of data stream for write to %s: %v\", p, err)\n\t\t}\n\n\t\tclient, err := p.getStorageClient(ctx)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\n\t\tw := client.Bucket(p.bucket).Object(p.key).NewWriter(ctx)\n\t\t// The upload is rejected if the data does not match this MD5 hash\n\t\tw.MD5 = md5Hash.HashValue\n\t\tw.ACL = objectACL\n\t\tif _, err := io.Copy(w, data); err != nil {\n\t\t\tw.Close()\n\t\t\treturn false, fmt.Errorf(\"error writing %s: %v\", p, err)\n\t\t}\n\t\tif err := w.Close(); err != nil {\n\t\t\treturn false, fmt.Errorf(\"error writing %s: %v\", p, err)\n\t\t}","sourceCodeStart":179,"sourceCodeEnd":215,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/util/pkg/vfs/gsfs.go#L179-L215","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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."],"exampleFix":"// before\nresp, _ := http.Get(url)\np.WriteFile(ctx, resp.Body, nil) // body is a non-seekable stream\n// after\nresp, _ := http.Get(url)\nb, _ := io.ReadAll(resp.Body)\nresp.Body.Close()\np.WriteFile(ctx, bytes.NewReader(b), nil)","handlingStrategy":"validation","validationCode":"// Verify the reader is truly seekable before calling WriteFile:\nfunc requireSeekable(r io.Reader) error {\n    rs, ok := r.(io.ReadSeeker)\n    if !ok {\n        return fmt.Errorf(\"reader %T is not seekable\", r)\n    }\n    if _, err := rs.Seek(0, io.SeekStart); err != nil {\n        return fmt.Errorf(\"reader %T cannot seek: %w\", r, err)\n    }\n    _, err := rs.Seek(0, io.SeekStart) // rewind again for the caller\n    return err\n}","typeGuard":"func isSeekable(r io.Reader) bool {\n    switch r.(type) {\n    case *bytes.Reader, *bytes.Buffer, *strings.Reader, *os.File:\n        return true // os.File only for regular files; pipes/FIFOs will fail\n    default:\n        return false\n    }\n}","tryCatchPattern":"if err := gsPath.WriteFile(ctx, data, acl); err != nil {\n    if strings.Contains(err.Error(), \"error seeking to start of data stream\") {\n        // materialize the stream and retry once\n        b, rerr := io.ReadAll(data)\n        if rerr != nil { return rerr }\n        if werr := gsPath.WriteFile(ctx, bytes.NewReader(b), acl); werr != nil {\n            return werr\n        }\n        return nil\n    }\n    return err\n}","preventionTips":["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."],"tags":["gcs","io","seek","kops","golang"],"backgroundTag":"non-seekable-stream","analyzedSha":"4c8573c808a73d578c5eadc86d410646ea0b0d73","analyzedAt":"2026-09-05T04:13:19.212Z","contentChangedAt":"2026-09-05T04:13:19.212Z","schemaVersion":2},"datasetVersion":"2026-09-12T07:17:12.445Z"}