{"record":{"id":"003a1a2907bb15ed","repo":"wavetermdev/waveterm","slug":"failed-to-copy-from-reader-v","errorCode":null,"errorMessage":"failed to copy from reader: %v","messagePattern":"failed to copy from reader: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/waveapp/waveapp.go","lineNumber":441,"sourceCode":"\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}\n\nfunc (c *Client) RegisterFilePrefixHandler(prefix string, optionProvider func(path string) (*FileHandlerOption, error)) {\n\tc.UrlHandlerMux.PathPrefix(prefix).HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\toption, err := optionProvider(r.URL.Path)\n\t\tif err != nil {\n\t\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\t\tif option == nil {\n\t\t\thttp.Error(w, \"no content available\", http.StatusNotFound)","sourceCodeStart":423,"sourceCodeEnd":459,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/waveapp/waveapp.go#L423-L459","documentation":"After writing the MIME-detected buffered prefix, ServeFileOption streams the remainder of an option.Reader io.Reader to the ResponseWriter with io.Copy. This error wraps any failure reading from the underlying reader or writing to the client during that copy. It means the response body was truncated and the client received an incomplete response.","triggerScenarios":"Calling ServeFileOption with FileHandlerOption{Reader: r} when r returns an error mid-stream: a closed pipe, a network/HTTP body read error, an os.File read failure, or a failed write to a disconnected client.","commonSituations":"Streaming content from an upstream HTTP response whose connection drops; reading from a process pipe (exec output) that exits early; serving from a network file; client disconnects partway through the transfer.","solutions":["Inspect the wrapped error: read errors mean the source reader failed — check the producer (upstream request, pipe, file) for logs/health.","If the reader is a response body or pipe, ensure it is fully available and not closed concurrently by another goroutine.","Treat write errors (EPIPE/ECONNRESET) as client cancellation and skip retrying.","Buffer small/known-size content into Data instead of a live Reader when reliability matters more than memory.","Add error logging including the stream's origin so truncated responses can be traced."],"exampleFix":"// before\nresp, _ := http.Get(upstreamURL)\ndefer resp.Body.Close()\nerr := waveapp.ServeFileOption(w, r, waveapp.FileHandlerOption{Reader: resp.Body}) // upstream drop -> truncated response\n// after\nresp, _ := http.Get(upstreamURL)\ndefer resp.Body.Close()\nif resp.StatusCode != http.StatusOK {\n    http.Error(w, \"upstream unavailable\", http.StatusBadGateway)\n    return\n}\nerr := waveapp.ServeFileOption(w, r, waveapp.FileHandlerOption{Reader: resp.Body})","handlingStrategy":"try-catch","validationCode":"if option.Reader == nil {\n    return fmt.Errorf(\"option.Reader must be set\")\n}\n// For known-size sources, verify readability up front\nif f, ok := option.Reader.(*os.File); ok {\n    if _, err := f.Stat(); err != nil {\n        return fmt.Errorf(\"reader source unreadable: %v\", err)\n    }\n}","typeGuard":"func isHealthyReader(r io.Reader) bool {\n    if f, ok := r.(*os.File); ok {\n        _, err := f.Stat()\n        return err == nil\n    }\n    return r != nil\n}","tryCatchPattern":"err := waveapp.ServeFileOption(w, r, opt)\nif err != nil {\n    select {\n    case <-r.Context().Done():\n        return // client canceled; ignore\n    default:\n    }\n    log.Printf(\"stream copy failed: %v\", err)\n    // headers already sent; cannot change status — just abort\n}","preventionTips":["Verify upstream sources (HTTP bodies, pipes) are alive and not closed concurrently.","For critical small payloads, load into option.Data instead of streaming a live Reader.","Include the stream origin in logs so truncated responses are traceable.","Remember the status code is already sent; a mid-copy failure cannot become a 500."],"tags":["go","http","streaming","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"}