NousResearch/hermes-agent · error · LSPProtocolError
cannot send {method!r}: stdin closed
Error message
cannot send {method!r}: stdin closed What it means
LSPProtocolError raised by _send_request when the server process is gone or its stdin pipe is closing — the request can never be delivered. It fires before any bytes are written, distinguishing it from the BrokenPipe 'send failed' variant: here the client already knows the transport is dead.
Source
Thrown at agent/lsp/client.py:513
proc.terminate()
try:
await asyncio.wait_for(proc.wait(), timeout=SHUTDOWN_GRACE)
except asyncio.TimeoutError:
try:
proc.kill()
await proc.wait()
except ProcessLookupError:
pass
except ProcessLookupError:
pass
# ------------------------------------------------------------------
# request / notification plumbing
# ------------------------------------------------------------------
async def _send_request(self, method: str, params: Any) -> Any:
if self._proc is None or self._proc.stdin is None or self._proc.stdin.is_closing():
raise LSPProtocolError(f"cannot send {method!r}: stdin closed")
loop = asyncio.get_running_loop()
req_id = self._next_id
self._next_id += 1
fut: asyncio.Future = loop.create_future()
self._pending[req_id] = fut
try:
self._proc.stdin.write(encode_message(make_request(req_id, method, params)))
await self._proc.stdin.drain()
except (BrokenPipeError, ConnectionResetError, OSError) as e:
self._pending.pop(req_id, None)
raise LSPProtocolError(f"send failed for {method!r}: {e}") from e
try:
return await fut
finally:
self._pending.pop(req_id, None)
async def _send_request_with_retry(self, method: str, params: Any, *, timeout: float) -> Any:
"""Send a request, retrying on ``ContentModified`` (-32801).View on GitHub (pinned to c896c09c42)
Solutions
- Check client.is_running before issuing requests and restart the client if it exited.
- Register for the client's exit/error callback and fail fast in your own code when the server dies instead of continuing to send.
- Investigate why the server terminated (stderr is drained at debug level — enable debug logs to see the crash reason).
Example fix
# before
hover = await client.request("textDocument/hover", params) # may raise: stdin closed
# after
if not client.is_running:
await client.start() # or recreate the client
hover = await client.request("textDocument/hover", params) Defensive patterns
Strategy: try-catch
Validate before calling
def can_send(client) -> bool:
return (
client.is_running
and client._proc is not None
and client._proc.stdin is not None
and not client._proc.stdin.is_closing()
) Try / catch
try:
result = await client.request(method, params)
except LSPProtocolError as e:
if "stdin closed" in str(e) or "send failed" in str(e):
client = await restart_client(client) # fresh process + re-open docs
result = await client.request(method, params)
else:
raise Prevention
- Check is_running before each request batch.
- Subscribe to the client's exit notification to fail fast instead of sending into a dead pipe.
- Never reuse a client object across stop()/start() cycles.
When it happens
Trigger: Calling any request API (hover, completions, definitions) after the server crashed, after stop() was called, or after the reader loop detected EOF and tore down the process while pending futures were never awaited by the caller.
Common situations: Server process killed by OOM or by the user mid-session; reusing a client object across a restart; requests racing shutdown in tests.
Related errors
- LSP server binary not found: {cmd[0]} ({e})
- send failed for {method!r}: {e}
- client not running
- unexpected EOF while reading LSP headers (partial={e.partial
- truncated LSP body: expected {n} bytes, got {len(e.partial)}
AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14).
Data as JSON: /api/errors/aacbec76c53f3444.
Report an issue: GitHub.