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
- 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).
- 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.
- If serving a path on disk, prefer FilePath (which uses http.ServeFile) over File so the library handles open/read/retry itself.
- Check disk/network health (dmesg, mount status) if read errors are syscall-level.
- 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
- Never close the File handle concurrently; close it only after ServeFileOption returns.
- Prefer FilePath over File when serving on-disk paths so http.ServeFile manages the lifecycle.
- Log client-disconnect write errors at debug level to avoid false alarms.
- Monitor disk/mount health if serving from network storage.
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
- failed to copy from reader: %v
- missing zone file info for ${zoneId}:${fileName}
- beginning of file
- wcloud endpoint not set
- wcloud ping endpoint not set
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/eb9016aa3ed98e81.
Report an issue: GitHub.