# Buggy code
def copy_file(self, local_file, remote_file, recurse=False, copy_args=None):
    """Copy local file to remote file in parallel

    This function returns a list of greenlets which can be
    `join`-ed on to wait for completion.

    :py:func:`gevent.joinall` function may be used to join on all greenlets
    and will also raise exceptions from them if called with
    ``raise_error=True`` - default is `False`.

    Alternatively call `.get` on each greenlet to raise any exceptions from
    it.

    Exceptions listed here are raised when
    either ``gevent.joinall(<greenlets>, raise_error=True)`` is called
    or ``.get`` is called on each greenlet, not this function itself.

    :param local_file: Local filepath to copy to remote host
    :type local_file: str
    :param remote_file: Remote filepath on remote host to copy file to
    :type remote_file: str
    :param recurse: Whether or not to descend into directories recursively.
    :type recurse: bool
    :param copy_args: (Optional) format local_file and remote_file strings
      with per-host arguments in ``copy_args``. ``copy_args`` length must
      equal length of host list -
      :py:class:`pssh.exceptions.HostArgumentException` is raised otherwise
    :type copy_args: tuple or list

    :rtype: List(:py:class:`gevent.Greenlet`) of greenlets for remote copy
      commands

    :raises: :py:class:`ValueError` when a directory is supplied to
      local_file and recurse is not set
    :raises: :py:class:`pssh.exceptions.HostArgumentException` on number of
      per-host copy arguments not equal to number of hosts
    :raises: :py:class:`IOError` on I/O errors writing files
    :raises: :py:class:`OSError` on OS errors like permission denied

    .. note ::

      Remote directories in `remote_file` that do not exist will be
      created as long as permissions allow.

    """
    if copy_args:
        try:
            return [
                self.pool.spawn(
                    self._copy_file,
                    host,
                    local_file % copy_args[host_i],
                    remote_file % copy_args[host_i],
                    {"recurse": recurse},
                )
                for host_i, host in enumerate(self.hosts)
            ]
        except IndexError:
            raise HostArgumentException(
                "Number of per-host copy arguments provided does not match "
                "number of hosts"
            )
    else:
        return [
            self.pool.spawn(
                self._copy_file, host, local_file, remote_file, {"recurse": recurse}
            )
            for host in self.hosts
        ]


# Traceback (ValueError)
Traceback (most recent call last):
File "./aws-instances", line 172, in <module>
main()
File "./aws-instances", line 73, in main
facts = remote_facts(ips)
File "./aws-instances", line 146, in remote_facts
out_kern = client.run_command('uname -r')
File "/home/user/do-aws-ec2-facts/aws/lib64/python3.6/site-packages/pssh/clients/native/parallel.py", line 223, in run_command
greenlet_timeout=greenlet_timeout)
File "/home/user/do-aws-ec2-facts/aws/lib64/python3.6/site-packages/pssh/clients/base_pssh.py", line 96, in run_command
self.get_output(cmd, output, timeout=greenlet_timeout)
File "/home/user/do-aws-ec2-facts/aws/lib64/python3.6/site-packages/pssh/clients/base_pssh.py", line 141, in get_output
(channel, host, stdout, stderr, stdin) = cmd.get(timeout=timeout)
File "src/gevent/greenlet.py", line 709, in gevent._greenlet.Greenlet.get
File "src/gevent/greenlet.py", line 317, in gevent._greenlet.Greenlet._raise_exception
File "/home/user/do-aws-ec2-facts/aws/lib64/python3.6/site-packages/gevent/_compat.py", line 47, in reraise
raise value.with_traceback(tb)
File "src/gevent/greenlet.py", line 766, in gevent._greenlet.Greenlet.run
File "/home/user/do-aws-ec2-facts/aws/lib64/python3.6/site-packages/pssh/clients/native/parallel.py", line 237, in _run_command
raise ex
File "/home/user/do-aws-ec2-facts/aws/lib64/python3.6/site-packages/pssh/clients/native/parallel.py", line 233, in _run_command
use_pty=use_pty, encoding=encoding, timeout=timeout)
File "/home/user/do-aws-ec2-facts/aws/lib64/python3.6/site-packages/pssh/clients/native/single.py", line 442, in run_command
channel = self.execute(_command, use_pty=use_pty)
File "/home/user/do-aws-ec2-facts/aws/lib64/python3.6/site-packages/pssh/clients/native/single.py", line 324, in execute
channel = self.open_session() if channel is None else channel
File "/home/user/do-aws-ec2-facts/aws/lib64/python3.6/site-packages/pssh/clients/native/single.py", line 297, in open_session
wait_select(self.session)
File "pssh/native/_ssh2.pyx", line 171, in pssh.native._ssh2.wait_select
File "/home/user/do-aws-ec2-facts/aws/lib64/python3.6/site-packages/gevent/select.py", line 145, in select
sel_results = _original_select(rlist, wlist, xlist, 0)
ValueError: filedescriptor out of range in select()

# Fixed code
def copy_file(self, local_file, remote_file, recurse=False, copy_args=None):
    """Copy local file to remote file in parallel

    This function returns a list of greenlets which can be
    `join`-ed on to wait for completion.

    :py:func:`gevent.joinall` function may be used to join on all greenlets
    and will also raise exceptions from them if called with
    ``raise_error=True`` - default is `False`.

    Alternatively call `.get` on each greenlet to raise any exceptions from
    it.

    Exceptions listed here are raised when
    either ``gevent.joinall(<greenlets>, raise_error=True)`` is called
    or ``.get`` is called on each greenlet, not this function itself.

    :param local_file: Local filepath to copy to remote host
    :type local_file: str
    :param remote_file: Remote filepath on remote host to copy file to
    :type remote_file: str
    :param recurse: Whether or not to descend into directories recursively.
    :type recurse: bool
    :param copy_args: (Optional) format local_file and remote_file strings
      with per-host arguments in ``copy_args``. ``copy_args`` length must
      equal length of host list -
      :py:class:`pssh.exceptions.HostArgumentException` is raised otherwise
    :type copy_args: tuple or list

    :rtype: List(:py:class:`gevent.Greenlet`) of greenlets for remote copy
      commands

    :raises: :py:class:`ValueError` when a directory is supplied to
      local_file and recurse is not set
    :raises: :py:class:`pssh.exceptions.HostArgumentException` on number of
      per-host copy arguments not equal to number of hosts
    :raises: :py:class:`IOError` on I/O errors writing files
    :raises: :py:class:`OSError` on OS errors like permission denied

    .. note ::

      Remote directories in `remote_file` that do not exist will be
      created as long as permissions allow.

    """
    if copy_args:
        try:
            return [
                self.pool.spawn(
                    self._copy_file,
                    host_i,
                    host,
                    local_file % copy_args[host_i],
                    remote_file % copy_args[host_i],
                    {"recurse": recurse},
                )
                for host_i, host in enumerate(self.hosts)
            ]
        except IndexError:
            raise HostArgumentException(
                "Number of per-host copy arguments provided does not match "
                "number of hosts"
            )
    else:
        return [
            self.pool.spawn(
                self._copy_file,
                host_i,
                host,
                local_file,
                remote_file,
                {"recurse": recurse},
            )
            for host_i, host in enumerate(self.hosts)
        ]