wavetermdev/waveterm · error

stream file protocol error, first pk fileinfo is empty

Error message

stream file protocol error, first pk fileinfo is empty

What it means

The fileshare stream protocol requires the first packet from the remote to carry the FileInfo (resp.Info). ReadFileStream enforces this; if the first packet arrives with a nil Info it means the remote violated the protocol, so the stream is aborted with this error.

Source

Thrown at pkg/remote/fileshare/fsutil/fsutil.go:91

	for {
		select {
		case <-ctx.Done():
			return fmt.Errorf("context cancelled: %v", context.Cause(ctx))
		case respUnion, ok := <-readCh:
			if !ok {
				drain = false
				return nil
			}
			if respUnion.Error != nil {
				return respUnion.Error
			}
			resp := respUnion.Response
			if firstPk {
				firstPk = false
				// first packet has the fileinfo
				if resp.Info == nil {
					return fmt.Errorf("stream file protocol error, first pk fileinfo is empty")
				}
				fileData = &resp
				if fileData.Info.IsDir {
					isDir = true
				}
				fileInfoCallback(*fileData.Info)
				continue
			}
			if isDir {
				if len(resp.Entries) == 0 {
					continue
				}
				if resp.Data64 != "" {
					return fmt.Errorf("stream file protocol error, directory entry has data")
				}
				if err := dirCallback(resp.Entries); err != nil {
					return err
				}

View on GitHub (pinned to a4447c1563)

Solutions

  1. Retry the read — a deletion race usually succeeds on a second attempt
  2. Verify the file still exists on the remote before streaming
  3. Upgrade wsh on both client and remote to matching versions
  4. Check remote-side logs for responder errors at stream start

Example fix

// before
data, err := fsutil.ReadStreamToFileData(ctx, ctl, path) // protocol error
// after
if exists, _ := remoteFileExists(ctx, ctl, path); !exists {
    return fmt.Errorf("remote file %q gone, skipping", path)
}
data, err := fsutil.ReadStreamToFileData(ctx, ctl, path)
Defensive patterns

Strategy: retry

Validate before calling

if ok, _ := remoteExists(ctx, ctl, path); !ok {
    return fmt.Errorf("remote path %q no longer exists", path)
}

Type guard

func hasFileInfo(resp *fileshare.StreamResponse) bool { return resp != nil && resp.Info != nil }

Try / catch

data, err := fsutil.ReadStreamToFileData(ctx, ctl, path)
if err != nil && strings.Contains(err.Error(), "first pk fileinfo is empty") {
    time.Sleep(250 * time.Millisecond) // deletion race: retry once
    data, err = fsutil.ReadStreamToFileData(ctx, ctl, path)
}

Prevention

When it happens

Trigger: ReadStreamToFileData streaming a path where the remote responder sends a first packet without file info — the remote file vanished between stat and stream, or the remote side (older/mismatched wsh) does not populate Info.

Common situations: File deleted or renamed on remote right after the read started; version mismatch between client and remote wsh fileshare implementations; race with another writer replacing the file.

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/eaa47b68561a6b7e. Report an issue: GitHub.