{"record":{"id":"7ffaee08617c3f93","repo":"amir20/dozzle","slug":"eof-error-while-converting-grpc-to-error-w","errorCode":null,"errorMessage":"EOF error while converting gRPC to error: %w","messagePattern":"EOF error while converting gRPC to error: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/agent/client.go","lineNumber":118,"sourceCode":"\tif len(parts) == 3 {\n\t\tgroup = parts[2]\n\t}\n\n\treturn parts[0], name, group, nil\n}\n\nfunc rpcErrToErr(err error) error {\n\tif err == nil {\n\t\treturn nil\n\t}\n\n\tstatus, ok := status.FromError(err)\n\tif !ok {\n\t\treturn err\n\t}\n\n\tif status.Code() == codes.Unknown && status.Message() == \"EOF\" {\n\t\treturn fmt.Errorf(\"EOF error while converting gRPC to error: %w\", io.EOF)\n\t}\n\n\tswitch status.Code() {\n\tcase codes.Canceled:\n\t\treturn fmt.Errorf(\"canceled: %v with %w\", status.Message(), context.Canceled)\n\tcase codes.DeadlineExceeded:\n\t\treturn fmt.Errorf(\"deadline exceeded: %v with %w\", status.Message(), context.DeadlineExceeded)\n\tcase codes.Unknown:\n\t\treturn fmt.Errorf(\"unknown error: %v with %w\", status.Message(), err)\n\tcase codes.OK:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unknown code: %v with %w\", status.Code(), err)\n\t}\n}\n\nfunc (c *Client) LogsBetweenDates(ctx context.Context, containerID string, since time.Time, until time.Time, std container.StdType) (<-chan *container.LogEvent, error) {\n\tstream, err := c.client.LogsBetweenDates(ctx, &pb.LogsBetweenDatesRequest{","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/amir20/dozzle/blob/d9463cbe21874e44ab79db6fa63e746ca7d22928/internal/agent/client.go#L100-L136","documentation":"rpcErrToErr translates a gRPC status with code Unknown and message \"EOF\" into a wrapped io.EOF. A server-side streaming handler returning io.EOF surfaces over gRPC as Unknown/\"EOF\", and this wrapper makes the underlying EOF visible (with %w) so callers can detect normal stream end.","triggerScenarios":"Any streaming RPC call (StreamEvents, StreamStats, StreamNewContainers, sendLogs) whose server handler returned io.EOF: the agent-side stream generator closed cleanly but the server returned the EOF as a status error instead of nil.","commonSituations":"Agent restarting mid-stream; watching logs of a container whose stream ended; agent version where ListContainers/Events handler returns EOF on client disconnect; transient EOF right after connect during agent startup.","solutions":["Treat errors.Is(err, io.EOF) as a clean stream end and reconnect/resubscribe with backoff","Upgrade agent and server to matching versions so handlers return nil instead of EOF on stream end","Check agent container health/restarts (`docker ps`, logs) if EOFs occur repeatedly","Use the client's auto-reconnect flows (container store resubscribes) rather than treating EOF as fatal"],"exampleFix":"// before\nerr := <-errCh\nif err != nil { log.Fatal().Err(err).Msg(\"stream failed\") }\n// after\nerr := <-errCh\nif errors.Is(err, io.EOF) {\n    time.Sleep(time.Second) // backoff and resubscribe\n    go resubscribe()\n    return\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err != nil {\n    if errors.Is(err, io.EOF) {\n        time.Sleep(backoff)\n        go resubscribe() // clean stream end, reconnect\n        return\n    }\n    log.Error().Err(err).Msg(\"stream failed\")\n}","preventionTips":["Match agent and server versions so handlers end streams with nil, not EOF","Always treat errors.Is(err, io.EOF) from agent streams as reconnectable","Monitor agent container restart counts; frequent EOFs often accompany restarts"],"tags":["go","grpc","streaming","agent"],"backgroundTag":"grpc-stream-eof","analyzedSha":"d9463cbe21874e44ab79db6fa63e746ca7d22928","analyzedAt":"2026-09-07T10:08:55.855Z","contentChangedAt":"2026-09-07T10:08:55.855Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}