NousResearch/hermes-agent · error · RuntimeError
codex app-server client is closed
Error message
codex app-server client is closed
What it means
RuntimeError('codex app-server client is closed') from CodexAppServerClient._send() (agent/transports/codex_app_server.py:303). Every outgoing frame goes through _send, which refuses to write once _closed is set (by close()); it is a use-after-close guard on the client's stdin pipe.
Source
Thrown at agent/transports/codex_app_server.py:303
with self._stderr_lock:
return list(self._stderr_lines[-n:])
def is_alive(self) -> bool:
return self._proc.poll() is None
# ---------- internals ----------
def _take_id(self) -> int:
# JSON-RPC ids only need to be unique per-connection. A simple
# monotonically increasing int is the common choice and matches what
# codex's own clients use.
rid = self._next_id
self._next_id += 1
return rid
def _send(self, obj: dict) -> None:
if self._closed:
raise RuntimeError("codex app-server client is closed")
if self._proc.stdin is None:
raise RuntimeError("codex app-server stdin not available")
try:
self._proc.stdin.write((json.dumps(obj) + "\n").encode("utf-8"))
self._proc.stdin.flush()
except (BrokenPipeError, ValueError) as exc:
raise RuntimeError(
f"codex app-server stdin closed unexpectedly: {exc}"
) from exc
def _read_stdout(self) -> None:
if self._proc.stdout is None:
return
try:
for line in iter(self._proc.stdout.readline, b""):
if not line:
break
line = line.strip()View on GitHub (pinned to c896c09c42)
Solutions
- Order operations so no sends happen after close(): cancel/await pending interactions before closing.
- Check the client's closed state before replying to server-initiated requests and drop stale replies.
- If a new conversation is needed, spawn a new client instead of reusing a closed one.
Example fix
# before
client.close()
client.respond(req_id, {"decision": "approved"}) # RuntimeError
# after
if not client._closed: # or add a public `closed` property
client.respond(req_id, {"decision": "approved"}) Defensive patterns
Strategy: validation
Validate before calling
def can_send(client) -> bool:
return not client._closed and client._proc.stdin is not None Try / catch
try:
client.respond(req_id, result)
except RuntimeError as exc:
if "closed" in str(exc):
logger.debug("dropping reply to %r: client closed", req_id)
return
raise Prevention
- Sequence shutdown last: drain pending approvals/responses before close().
- Treat close() as terminal — spawn a new client for new work.
- Check the closed flag before replying to server-initiated requests.
When it happens
Trigger: Calling request()/notify()/respond() after close() — e.g. a response to a server-initiated approval arriving after the session teardown closed the client, or a background thread racing shutdown.
Common situations: Approval/reply flows where the UI answers after the timeout path already closed the client; concurrent shutdown and in-flight requests; forgetting that close() is terminal for the whole client, not per-thread.
Related errors
- already initialized
- codex app-server method {method!r} timed out after {timeout}
- codex app-server error {code}: {message}
- codex app-server stdin not available
- -32603
AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14).
Data as JSON: /api/errors/f42ac4fd72242aa7.
Report an issue: GitHub.