wavetermdev/waveterm · error

failed to copy from file: %v

Error message

failed to copy from file: %v

What it means

ServeFileOption streams the body of a FileHandlerOption that supplies an open file (option.File) to the http.ResponseWriter via io.Copy. This error wraps any read error from that underlying fs.File (or a write error to the response writer reported by io.Copy) while streaming the file body, after the Content-Type header has already been sent. It indicates the HTTP response body could not be fully delivered from the file source.

Source

Thrown at pkg/waveapp/waveapp.go:431

	case option.FilePath != "":
		filePath := wavebase.ExpandHomeDirSafe(option.FilePath)
		http.ServeFile(w, r, filePath)

	case option.Data != nil:
		w.Header().Set("Content-Length", fmt.Sprintf("%d", len(option.Data)))
		w.WriteHeader(http.StatusOK)
		if _, err := w.Write(option.Data); err != nil {
			return fmt.Errorf("failed to write data: %v", err)
		}

	case option.File != nil:
		if bufferedData != nil {
			if _, err := w.Write(bufferedData); err != nil {
				return fmt.Errorf("failed to write buffered data: %v", err)
			}
		}
		if _, err := io.Copy(w, option.File); err != nil {
			return fmt.Errorf("failed to copy from file: %v", err)
		}

	case option.Reader != nil:
		if bufferedData != nil {
			if _, err := w.Write(bufferedData); err != nil {
				return fmt.Errorf("failed to write buffered data: %v", err)
			}
		}
		if _, err := io.Copy(w, option.Reader); err != nil {
			return fmt.Errorf("failed to copy from reader: %v", err)
		}

	default:
		return fmt.Errorf("no content available")
	}

	return nil
}

View on GitHub (pinned to a4447c1563)

Solutions

  1. Verify the fs.File passed in option.File is still open and owned solely by the handler for the duration of ServeFileOption (do not close it early or concurrently).
  2. Check the wrapped %v error: if it is a write error like 'broken pipe' or 'connection reset', the client disconnected — this is usually benign and should be logged, not treated as a server bug.
  3. If serving a path on disk, prefer FilePath (which uses http.ServeFile) over File so the library handles open/read/retry itself.
  4. Check disk/network health (dmesg, mount status) if read errors are syscall-level.
  5. Ensure the file position is at 0 if the handle was reused; data already consumed by MIME detection is handled internally via bufferedData, but external seeks are not.

Example fix

// before
f, _ := myCache.Open(path)
defer f.Close()
go myCache.Invalidate(path) // may close f concurrently
err := waveapp.ServeFileOption(w, r, waveapp.FileHandlerOption{File: f})
// after
f, _ := myCache.Open(path)
err := waveapp.ServeFileOption(w, r, waveapp.FileHandlerOption{File: f})
f.Close() // close only after serving completes; no concurrent closer
Defensive patterns

Strategy: try-catch

Validate before calling

if option.File == nil {
    return fmt.Errorf("option.File must be set")
}
// ensure no concurrent closer: document ownership; optionally stat the file first
if f, ok := option.File.(*os.File); ok {
    if _, err := f.Stat(); err != nil {
        return fmt.Errorf("file handle unusable before serving: %v", err)
    }
}

Type guard

func isUsableOSFile(f fs.File) (*os.File, bool) {
    of, ok := f.(*os.File)
    if !ok || of == nil {
        return nil, false
    }
    _, err := of.Stat()
    return of, err == nil
}

Try / catch

err := waveapp.ServeFileOption(w, r, opt)
if err != nil {
    if errors.Is(err, syscall.EPIPE) || errors.Is(err, syscall.ECONNRESET) {
        return // client disconnected; benign
    }
    http.Error(w, "failed to serve file", http.StatusInternalServerError)
    log.Printf("serve file failed: %v", err)
}

Prevention

When it happens

Trigger: Calling ServeFileOption with FileHandlerOption{File: <opened fs.File>} when the underlying file read fails mid-stream: file was closed by another goroutine, file descriptor hit an I/O error, file deleted/unmounted during read, or the client disconnected so writing to the ResponseWriter fails.

Common situations: An app-server handler serving a file the user then deletes or replaces (e.g. a draft app asset regenerated during a build); disk errors or NFS/network mounts dropping out; client aborts the request (canceling the download) causing the write half of io.Copy to fail.

Related errors


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