#2351·fabric

ThreadException(OSError(Socket is closed)) when program exits without reading input stream

Author: sporksmithCreated Nov 11, 2025Updated Aug 10, 2026
LabelsBugGroup

When executing a program that exits without reading the input stream, we get a ThreadException, wrapping an OSError, due to the socket being closed when we try to write to it.

To reproduce

Example script:

python
import fabric
import io

conn = fabric.connection.Connection('localhost')
conn.run('exit 0', in_stream=io.StringIO("blah"))

and output:

Traceback (most recent call last):
  File "/home/jnewsome/tmp/repro.py", line 5, in <module>
    conn.run('exit 0', in_stream=io.StringIO("blah"))
  File "/home/jnewsome/.local/lib/python3.10/site-packages/decorator.py", line 235, in fun
    return caller(func, *(extras + args), **kw)
  File "/home/jnewsome/.local/lib/python3.10/site-packages/fabric/connection.py", line 23, in opens
    return method(self, *args, **kwargs)
  File "/home/jnewsome/.local/lib/python3.10/site-packages/fabric/connection.py", line 763, in run
    return self._run(self._remote_runner(), command, **kwargs)
  File "/home/jnewsome/.local/lib/python3.10/site-packages/invoke/context.py", line 113, in _run
    return runner.run(command, **kwargs)
  File "/home/jnewsome/.local/lib/python3.10/site-packages/fabric/runners.py", line 83, in run
    return super().run(command, **kwargs)
  File "/home/jnewsome/.local/lib/python3.10/site-packages/invoke/runners.py", line 395, in run
    return self._run_body(command, **kwargs)
  File "/home/jnewsome/.local/lib/python3.10/site-packages/invoke/runners.py", line 451, in _run_body
    return self.make_promise() if self._asynchronous else self._finish()
  File "/home/jnewsome/.local/lib/python3.10/site-packages/invoke/runners.py", line 503, in _finish
    raise ThreadException(thread_exceptions)
invoke.exceptions.ThreadException: 
Saw 1 exceptions within threads (OSError):


Thread args: {'kwargs': {'echo': None,
            'input_': <_io.StringIO object at 0x77872e163d90>,
            'output': <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>},
 'target': <bound method Runner.handle_stdin of <fabric.runners.Remote object at 0x77872c4759c0>>}

Traceback (most recent call last):

  File "/home/jnewsome/.local/lib/python3.10/site-packages/invoke/util.py", line 211, in run
    super().run()

  File "/usr/lib/python3.10/threading.py", line 953, in run
    self._target(*self._args, **self._kwargs)

  File "/home/jnewsome/.local/lib/python3.10/site-packages/invoke/runners.py", line 877, in handle_stdin
    self.write_proc_stdin(data)

  File "/home/jnewsome/.local/lib/python3.10/site-packages/invoke/runners.py", line 1017, in write_proc_stdin
    self._write_proc_stdin(data.encode(self.encoding))

  File "/home/jnewsome/.local/lib/python3.10/site-packages/fabric/runners.py", line 92, in _write_proc_stdin
    return self.channel.sendall(data)

  File "/home/jnewsome/.local/lib/python3.10/site-packages/paramiko/channel.py", line 844, in sendall
    sent = self.send(s)

  File "/home/jnewsome/.local/lib/python3.10/site-packages/paramiko/channel.py", line 799, in send
    return self._send(s, m)

  File "/home/jnewsome/.local/lib/python3.10/site-packages/paramiko/channel.py", line 1196, in _send
    raise socket.error("Socket is closed")

OSError: Socket is closed

We get the same output if we change the command from exit 0 to exit 1; i.e. we get the ThreadException/OSError instead of any sort of an UnexpectedExit.

Expected behavior

One of:

  • No exception surfaced to the run caller at all related to the process not reading the full input. (I'd still expect an UnexpectedExit if it exited with a non-zero status code, whether or not it read the full input first)
  • Some clearer exception derived from invoke.failure.Failure; e.g. UnconsumedInput, that can be more-easily caught and handled. Preferably in combination with a parameter to run like allow_unconsumed_input allowing to suppress such errors.

Environment

python is a packaged version on popos:

$ python3 --version
Python 3.10.12

$ apt show python3
Package: python3
Version: 3.10.6-1~22.04.1
...

ssh and sshd is openssh:

$ ssh -V
OpenSSH_8.9p1 Ubuntu-3ubuntu0.13, OpenSSL 3.0.2 15 Mar 2022

$ apt show openssh-client
Package: openssh-client
Version: 1:8.9p1-3ubuntu0.13

$ sshd -V
unknown option -- V
OpenSSH_8.9p1 Ubuntu-3ubuntu0.13, OpenSSL 3.0.2 15 Mar 2022
...

$ apt show openssh-server
Package: openssh-server
Version: 1:8.9p1-3ubuntu0.13
...

fabric is the current latest:

$ pip show fabric
Name: fabric
Version: 3.2.2