NousResearch/hermes-agent · error · RuntimeError

codex app-server stdin not available

Error message

codex app-server stdin not available

What it means

RuntimeError('codex app-server stdin not available') from _send() (agent/transports/codex_app_server.py:305). It fires when self._proc.stdin is None, i.e. the subprocess was spawned without a stdin pipe, so there is no channel to write JSON-RPC frames to. This is a spawn-configuration bug in the caller, not a runtime failure of codex.

Source

Thrown at agent/transports/codex_app_server.py:305

    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()
                if not line:
                    continue

View on GitHub (pinned to c896c09c42)

Solutions

  1. Spawn the app-server with subprocess.Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=...) so the JSON-RPC framing over stdio works.
  2. Fail fast: validate _proc.stdin is not None right after spawn instead of on first send.
  3. Reuse the client's own spawn helper rather than constructing the process manually.

Example fix

# before
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)  # no stdin pipe

# after
proc = subprocess.Popen(
    cmd,
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
)
Defensive patterns

Strategy: validation

Validate before calling

import subprocess

def spawn_app_server(cmd: list[str]) -> subprocess.Popen:
    proc = subprocess.Popen(cmd, stdin=subprocess.PIPE,
                            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    assert proc.stdin is not None, "spawn must pipe stdin for JSON-RPC"
    return proc

Try / catch

try:
    client.request("initialize", {})
except RuntimeError as exc:
    if "stdin not available" in str(exc):
        raise RuntimeError("respawn the app-server with stdin=PIPE") from exc
    raise

Prevention

When it happens

Trigger: The Popen call that created the codex app-server process lacked stdin=PIPE (or explicitly set stdin=DEVNULL/None), then any request/notify hits the guard immediately.

Common situations: A custom spawn path (test harness, alternate launcher) copies a generic subprocess helper that only pipes stdout/stderr; refactoring the spawn code drops the stdin pipe; Windows-specific spawn flags accidentally overriding stdin.

Related errors


AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14). Data as JSON: /api/errors/f48b91b3f1c15c7f. Report an issue: GitHub.