mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-11-06 05:17:15 +00:00
fix(acp): a CLI that stops reading before the first request reports its exit text
The union run flaked test_alias_keeps_the_former_gh_extension_diagnosis: a CLI that dies at once (the old `gh copilot` extension) can close its stdin before ACPClient writes `initialize`, and the write raised a bare BrokenPipeError, skipping the early-exit path that reports the CLI's stderr (and the alias's gh-extension diagnosis). Pre-existing on main's CopilotACPClient. A broken pipe on a request write now waits briefly for the exit and falls into that path. Test drives the race deterministically (the fake CLI closes stdin before the client's first write).
This commit is contained in:
1 parent
8dddcc0101
commit
129f205a0e
2 files changed
+37
-2
No files matched your search
+6
-2
@@ -451,8 +451,12 @@ class ACPClient:
|
||||
def _request(method: str, params: dict[str, Any], *, text_parts: list[str] | None = None,
|
||||
reasoning_parts: list[str] | None = None) -> Any:
|
||||
request_id = next(request_ids)
|
||||
proc.stdin.write(json.dumps({"jsonrpc": "2.0", "id": request_id, "method": method, "params": params}) + "\n")
|
||||
proc.stdin.flush()
|
||||
try:
|
||||
proc.stdin.write(json.dumps({"jsonrpc": "2.0", "id": request_id, "method": method, "params": params}) + "\n")
|
||||
proc.stdin.flush()
|
||||
except BrokenPipeError: # the CLI stopped reading (dying): report its exit text, not a bare EPIPE
|
||||
with contextlib.suppress(subprocess.TimeoutExpired):
|
||||
proc.wait(timeout=5)
|
||||
deadline = session_deadline
|
||||
while time.monotonic() < deadline and proc.poll() is None:
|
||||
try:
|
||||
|
||||
@@ -14,6 +14,7 @@ import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
@@ -304,6 +305,36 @@ def test_cli_death_is_reported_as_a_crash_not_a_timeout(tmp_path):
|
||||
pass
|
||||
|
||||
|
||||
_STDIN_CLOSING_ACP_CLI = """import os, sys, time
|
||||
os.close(0) # stops reading: the client's first write hits a closed pipe
|
||||
open(sys.argv[1], "w").close()
|
||||
time.sleep(0.3)
|
||||
print('fatal: agent cannot start', file=sys.stderr)
|
||||
sys.exit(2)
|
||||
"""
|
||||
|
||||
|
||||
def test_cli_that_stops_reading_is_reported_with_its_exit_text(tmp_path):
|
||||
"""A CLI dying before it reads the first request: the write raised a bare BrokenPipeError, so the
|
||||
user never saw the CLI's own crash text (and the error read as a client bug)."""
|
||||
server = tmp_path / "stdin_closing_acp.py"
|
||||
server.write_text(_STDIN_CLOSING_ACP_CLI, encoding="utf-8")
|
||||
closed = tmp_path / "stdin-closed"
|
||||
client = _AcmeClient(command=sys.executable, args=[str(server), str(closed)], acp_cwd=str(tmp_path))
|
||||
real_spawn = client._spawn
|
||||
|
||||
def spawn_then_wait_for_the_close():
|
||||
proc = real_spawn()
|
||||
deadline = time.monotonic() + 30
|
||||
while not closed.exists() and time.monotonic() < deadline:
|
||||
time.sleep(0.01)
|
||||
return proc
|
||||
|
||||
client._spawn = spawn_then_wait_for_the_close
|
||||
with pytest.raises(RuntimeError, match="exited early: fatal: agent cannot start"):
|
||||
with client._session(30):
|
||||
pass
|
||||
|
||||
def test_model_discovery_does_not_allow_file_requests(tmp_path):
|
||||
"""``list_models`` opens a session only to read the catalog: an fs request on that path is refused."""
|
||||
target = tmp_path / "should-not-be-read.txt"
|
||||
|
||||
Reference in new issue
Block a user