{"record":{"id":"eb9016aa3ed98e81","repo":"wavetermdev/waveterm","slug":"failed-to-copy-from-file-v","errorCode":null,"errorMessage":"failed to copy from file: %v","messagePattern":"failed to copy from file: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/waveapp/waveapp.go","lineNumber":431,"sourceCode":"\tcase option.FilePath != \"\":\n\t\tfilePath := wavebase.ExpandHomeDirSafe(option.FilePath)\n\t\thttp.ServeFile(w, r, filePath)\n\n\tcase option.Data != nil:\n\t\tw.Header().Set(\"Content-Length\", fmt.Sprintf(\"%d\", len(option.Data)))\n\t\tw.WriteHeader(http.StatusOK)\n\t\tif _, err := w.Write(option.Data); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write data: %v\", err)\n\t\t}\n\n\tcase option.File != nil:\n\t\tif bufferedData != nil {\n\t\t\tif _, err := w.Write(bufferedData); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to write buffered data: %v\", err)\n\t\t\t}\n\t\t}\n\t\tif _, err := io.Copy(w, option.File); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to copy from file: %v\", err)\n\t\t}\n\n\tcase option.Reader != nil:\n\t\tif bufferedData != nil {\n\t\t\tif _, err := w.Write(bufferedData); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to write buffered data: %v\", err)\n\t\t\t}\n\t\t}\n\t\tif _, err := io.Copy(w, option.Reader); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to copy from reader: %v\", err)\n\t\t}\n\n\tdefault:\n\t\treturn fmt.Errorf(\"no content available\")\n\t}\n\n\treturn nil\n}","sourceCodeStart":413,"sourceCodeEnd":449,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/waveapp/waveapp.go#L413-L449","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nf, _ := myCache.Open(path)\ndefer f.Close()\ngo myCache.Invalidate(path) // may close f concurrently\nerr := waveapp.ServeFileOption(w, r, waveapp.FileHandlerOption{File: f})\n// after\nf, _ := myCache.Open(path)\nerr := waveapp.ServeFileOption(w, r, waveapp.FileHandlerOption{File: f})\nf.Close() // close only after serving completes; no concurrent closer","handlingStrategy":"try-catch","validationCode":"if option.File == nil {\n    return fmt.Errorf(\"option.File must be set\")\n}\n// ensure no concurrent closer: document ownership; optionally stat the file first\nif f, ok := option.File.(*os.File); ok {\n    if _, err := f.Stat(); err != nil {\n        return fmt.Errorf(\"file handle unusable before serving: %v\", err)\n    }\n}","typeGuard":"func isUsableOSFile(f fs.File) (*os.File, bool) {\n    of, ok := f.(*os.File)\n    if !ok || of == nil {\n        return nil, false\n    }\n    _, err := of.Stat()\n    return of, err == nil\n}","tryCatchPattern":"err := waveapp.ServeFileOption(w, r, opt)\nif err != nil {\n    if errors.Is(err, syscall.EPIPE) || errors.Is(err, syscall.ECONNRESET) {\n        return // client disconnected; benign\n    }\n    http.Error(w, \"failed to serve file\", http.StatusInternalServerError)\n    log.Printf(\"serve file failed: %v\", err)\n}","preventionTips":["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."],"tags":["go","http","file-io","io-copy"],"backgroundTag":"http-response-stream-write-failed","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}