NousResearch/hermes-agent · error · RuntimeError

Copilot ACP process did not expose stdin/stdout pipes.

Error message

Copilot ACP process did not expose stdin/stdout pipes.

What it means

The Copilot ACP subprocess spawned successfully but did not provide usable stdin/stdout pipes, which the JSON-RPC protocol requires. Hermes kills the process and raises this RuntimeError. In practice this only occurs with an exotic spawn configuration (e.g. a wrapper that closes or redirects standard streams) since Popen is called with explicit PIPEs.

Source

Thrown at agent/copilot_acp_client.py:529

                [self._acp_command] + self._acp_args,
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True, encoding='utf-8', errors='replace',
                bufsize=1,
                cwd=self._acp_cwd,
                env=_build_subprocess_env(),
                creationflags=windows_hide_flags(),
            )
        except FileNotFoundError as exc:
            raise RuntimeError(
                f"Could not start Copilot ACP command '{self._acp_command}'. "
                "Install GitHub Copilot CLI or set HERMES_COPILOT_ACP_COMMAND/COPILOT_CLI_PATH."
            ) from exc

        if proc.stdin is None or proc.stdout is None:
            proc.kill()
            raise RuntimeError("Copilot ACP process did not expose stdin/stdout pipes.")

        self.is_closed = False
        with self._active_process_lock:
            self._active_process = proc

        inbox: queue.Queue[dict[str, Any]] = queue.Queue()
        stderr_tail: deque[str] = deque(maxlen=40)

        def _stdout_reader() -> None:
            if proc.stdout is None:
                return
            for line in proc.stdout:
                try:
                    inbox.put(json.loads(line))
                except Exception:
                    inbox.put({"raw": line.rstrip("\n")})

        def _stderr_reader() -> None:

View on GitHub (pinned to c896c09c42)

Solutions

  1. Point HERMES_COPILOT_ACP_COMMAND directly at the real copilot binary, not a wrapper that touches stdio.
  2. Verify manually that the binary speaks line-delimited JSON-RPC on stdio: echo '<initialize request>' | copilot ... (it should respond on stdout).
  3. Update Copilot CLI in case of a version-specific stdio bug.
Defensive patterns

Strategy: validation

Validate before calling

# Smoke-test the command speaks stdio JSON-RPC before wiring it in:
import subprocess
p = subprocess.run([cmd, '--help'], capture_output=True, text=True, timeout=10)
assert p.returncode == 0, p.stderr  # a healthy binary at least runs

Try / catch

try:
    client = CopilotACPClient(...)
except RuntimeError as e:
    if 'did not expose stdin/stdout pipes' in str(e):
        # replace stdio-touching wrapper with the direct binary
        ...

Prevention

When it happens

Trigger: HERMES_COPILOT_ACP_COMMAND points at a wrapper script/binary that closes stdin/stdout, or a platform-level spawn anomaly where the PIPE file descriptors are not available (proc.stdin/proc.stdout is None).

Common situations: A custom ACP shim command that daemonizes or redirects stdio; unusual shells or process supervisors interfering with inherited file descriptors. Very rare on standard setups.

Related errors


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