{"record":{"id":"fb1c112d2633656d","repo":"ipfs/kubo","slug":"e-stream-error-trailer-header-value","errorCode":null,"errorMessage":"e (stream error trailer header value)","messagePattern":"e \\(stream error trailer header value\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/rpc/response.go","lineNumber":28,"sourceCode":"\t\"net/url\"\n\t\"os\"\n\n\t\"github.com/ipfs/boxo/files\"\n\tcmds \"github.com/ipfs/go-ipfs-cmds\"\n\tcmdhttp \"github.com/ipfs/go-ipfs-cmds/http\"\n)\n\ntype Error = cmds.Error\n\ntype trailerReader struct {\n\tresp *http.Response\n}\n\nfunc (r *trailerReader) Read(b []byte) (int, error) {\n\tn, err := r.resp.Body.Read(b)\n\tif err != nil {\n\t\tif e := r.resp.Trailer.Get(cmdhttp.StreamErrHeader); e != \"\" {\n\t\t\terr = errors.New(e)\n\t\t}\n\t}\n\treturn n, err\n}\n\nfunc (r *trailerReader) Close() error {\n\treturn r.resp.Body.Close()\n}\n\ntype Response struct {\n\tOutput io.ReadCloser\n\tError  *Error\n}\n\nfunc (r *Response) Close() error {\n\tif r.Output != nil {\n\n\t\t// drain output (response body)","sourceCodeStart":10,"sourceCodeEnd":46,"githubUrl":"https://github.com/ipfs/kubo/blob/329838acdfafae224582930457efe80aa217afc0/client/rpc/response.go#L10-L46","documentation":"trailerReader wraps the HTTP response body of streaming RPC calls; on a body read error it checks the X-Stream-Error trailer and, if present, replaces the underlying io error with the daemon's error string. Callers of any Exec/Decode that streams (e.g. ls, log tail, pin ls stream) see the server's error as the Read error.","triggerScenarios":"Any streaming RPC response (io.Copy on api.Request(...).Send, reading log tail or ls output) where the daemon encounters an error mid-stream and sets the StreamErrHeader trailer while closing the body — surfacing io.ReadAll/io.Copy as this error instead of io.ErrUnexpectedEOF.","commonSituations":"Daemon-side failures during long listings (context canceled on the server, internal errors), connections interrupted mid-response where the trailer still arrived, or reading a streamed response to completion after the daemon aborted the request.","solutions":["Treat the message as the daemon's failure reason and fix the server-side cause; do not retry assuming a plain network error.","Retry the whole request if the message indicates a transient daemon condition (canceled context, temporary resource exhaustion).","Check daemon logs at the matching timestamp for the underlying stack trace.","Use the request's context to enforce your own client-side timeout so long streams fail predictably."],"exampleFix":"// before\nresp, _ := api.Request(\"pin/ls\").Send(ctx)\nio.Copy(buf, resp.Output) // opaque error from trailer\n// after\nresp, _ := api.Request(\"pin/ls\").Send(ctx)\nif _, err := io.Copy(buf, resp.Output); err != nil {\n    return fmt.Errorf(\"pin/ls stream failed: %w\", err) // message is the daemon's trailer error\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"resp, err := api.Request(\"pin/ls\").Send(ctx)\nif err != nil { return err }\n_, err = io.Copy(buf, resp.Output)\nif err != nil {\n    // err text is the daemon's X-Stream-Error trailer value\n    return fmt.Errorf(\"rpc stream failed: %w\", err)\n}","preventionTips":["Set client-side context timeouts on all streaming RPC calls","Check daemon logs when a stream error message appears","Distinguish trailer errors from io.EOF/io.ErrUnexpectedEOF before retry logic","Keep daemon and client updated; stream error formats can evolve"],"tags":["http-rpc","stream-error","trailer","io"],"backgroundTag":"server-error-passthrough","analyzedSha":"329838acdfafae224582930457efe80aa217afc0","analyzedAt":"2026-09-03T18:30:52.135Z","contentChangedAt":"2026-09-03T18:30:52.135Z","schemaVersion":2},"datasetVersion":"2026-09-11T00:17:11.886Z"}