When handling failures, make error semantics explicit and consistent across the stack:

Example pattern (retry classification):

func isRetryableTransportError(err error) bool {
	if err == nil {
		return false
	}
	if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
		return true
	}
	var netErr net.Error
	return errors.As(err, &netErr) && (netErr.Timeout() || netErr.Temporary())
}

And ensure callers only retry when the failure occurs before output is committed, with tests covering Timeout, Temporary, and the “neither set” negative case.