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

  1. 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.
  2. 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.
  3. 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.
  4. 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

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


AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05). Data as JSON: /api/errors/ea244d69c91e5da9. Report an issue: GitHub.