An EOFError
from Channel.recv()
in Paramiko indicates that the SSH channel was closed before all expected data arrived. This guide covers strategies—framing protocols, timeouts, retries, and chunked reads—to detect and recover from incomplete transfers reliably.
1. Understand EOFError Scenarios
- Remote process exits abruptly before writing full output.
- Network interruption or server-side channel close.
- Blocking
recv()
without proper timeout or framing.
2. Set Timeouts and Non-Blocking Mode
Configure channel timeouts to avoid indefinite blocks:
channel = ssh_client.get_transport().open_session() channel.exec_command('long_running_command') channel.settimeout(10.0) # seconds try: data = channel.recv(1024) except socket.timeout: print("Read timed out")
Tip: In non-blocking mode, catch
socket.timeout
to break loops and retry[socket API PDF].
3. Implement a Framing Protocol
Prefix each data block with its length so the client knows exactly how many bytes to expect:
# Server side (remote script) data = b"..." length = len(data) print(f"{length:08d}", end="") # 8-digit length header sys.stdout.flush() sys.stdout.buffer.write(data) # Client side header = channel.recv(8) expected = int(header) buffer = bytearray() while len(buffer) < expected: chunk = channel.recv(min(4096, expected - len(buffer))) if not chunk: raise EOFError("Channel closed early") buffer.extend(chunk) print("Received full payload")
This ensures deterministic reads and avoids unexpected EOF.
4. Chunked Read with Retry Logic
Read in small chunks and retry on EOFError
up to a limit:
def safe_recv(channel, total_bytes, chunk_size=1024, max_retries=3): buffer = bytearray() retries = 0 while len(buffer) < total_bytes: try: chunk = channel.recv(min(chunk_size, total_bytes - len(buffer))) if not chunk: raise EOFError("Channel closed") buffer.extend(chunk) except EOFError as e: if retries >= max_retries: raise retries += 1 time.sleep(1) # backoff return bytes(buffer)
5. Use recv_ready()
to Poll Data
Check channel readiness before receiving to avoid blocking reads:
import select while not channel.exit_status_ready(): if channel.recv_ready(): data = channel.recv(2048) process(data) else: # avoid busy loop time.sleep(0.1)
Note: Use
channel.recv_ready()
and channel.exit_status_ready()
to know when to stop reading[Paramiko Docs PDF].
6. Graceful Channel Closure
After reading expected data, explicitly close the channel:
channel.shutdown_read() channel.shutdown_write() channel.close()
7. Summary Checklist
- Set socket and channel timeouts to detect delays.
- Implement framing (length headers) for deterministic reads.
- Read in chunks with retry/backoff on EOFError.
- Use
recv_ready()
and non-blocking mode to poll data. - Close channels gracefully to signal end-of-data.