From d8e964a1907cbf70fba997ab840b69f857fcbf8d Mon Sep 17 00:00:00 2001 From: EMRG Evolution Date: Sun, 9 Aug 2026 09:57:09 +0800 Subject: [PATCH] emrg: stop swallowing AuthError + programming errors in stale check (G129) --- Agent.md | 2 +- README.cn.md | 2 +- README.md | 2 +- emrg/client/daemon_manager.py | 12 ++++++++++-- tests/test_daemon_manager.py | 29 +++++++++++++++++++++++++++++ 5 files changed, 42 insertions(+), 5 deletions(-) diff --git a/Agent.md b/Agent.md index 0c592aaf..67e761d6 100644 --- a/Agent.md +++ b/Agent.md @@ -93,7 +93,7 @@ Community needs voiced in HN agent-UI discussions map directly to EMRG's design: pkill -f "emrg.server"; rm -f ~/.emrg/emrgd.port; python -m emrg ``` -Python: `uv run pytest tests/ -v` (639) — import check: `uv run python -c "from emrg.client.app import run_client"` +Python: `uv run pytest tests/ -v` (641) — import check: `uv run python -c "from emrg.client.app import run_client"` GUI: `cd emrg/gui && npm test` (96: 22 daemon_client + 22 app-commands + 27 renderer smoke + 15 i18n + 7 integration + 3 commands) — syntax: `node --check main.js preload.js daemon_client.js renderer/js/*.js` CI: `uv run pytest` + GUI tests + **actionlint workflow lint** (`rhysd/actionlint@v1.7.12` gate, #444 — workflow 解析错误在 PR CI 即失败,如 `if:` secrets 上下文) Re-trigger: `scripts/re-trigger-ci.sh [branch]` (workflow_dispatch, #527 — 替代空 commit 重触发:Actions outage 会整段丢弃 push 事件,dispatch 走 API 路径不受影响) diff --git a/README.cn.md b/README.cn.md index e6831dda..072198a6 100644 --- a/README.cn.md +++ b/README.cn.md @@ -274,7 +274,7 @@ EMRG 不只是追赶——它自己追上来。 git clone https://github.com/argszero/emrg.git cd emrg uv sync # 安装依赖 -uv run pytest tests/ -v # 跑测试(当前 639 项) +uv run pytest tests/ -v # 跑测试(当前 641 项) uv run python -m emrg # 启动 TUI # CI 含 actionlint workflow 门禁(#444):workflow 解析错误在 PR 即失败 diff --git a/README.md b/README.md index 1f06013b..724528e9 100644 --- a/README.md +++ b/README.md @@ -273,7 +273,7 @@ EMRG doesn't just keep up — it catches up on its own. git clone https://github.com/argszero/emrg.git cd emrg uv sync # install deps -uv run pytest tests/ -v # run tests (currently 639 items) +uv run pytest tests/ -v # run tests (currently 641 items) uv run python -m emrg # launch TUI # CI includes actionlint workflow gate (#444): workflow parse errors fail PR CI diff --git a/emrg/client/daemon_manager.py b/emrg/client/daemon_manager.py index 4e6709fe..6bf72b4f 100644 --- a/emrg/client/daemon_manager.py +++ b/emrg/client/daemon_manager.py @@ -22,12 +22,14 @@ from typing import AsyncIterator from emrg.connect import ( + AuthError, cleanup_server, connect_to_server, get_server_path, is_server_running_sync, ) from emrg.protocol import TaskRequest +from websockets.exceptions import ConnectionClosed logger = logging.getLogger(__name__) @@ -166,8 +168,14 @@ async def check_and_restart_if_stale() -> None: except (ProcessLookupError, OSError): pass except (ConnectionRefusedError, FileNotFoundError, OSError, json.JSONDecodeError, - asyncio.TimeoutError, Exception): - pass # Server not reachable — connect_to_server will handle + asyncio.TimeoutError, ConnectionClosed): + # G129 (rant 2026-08-09T08:03:46): only genuinely transient connection + # failures are swallowed here — connect_to_server in ensure_connected() + # will surface the real error. AuthError and programming errors are NOT + # in this list: a token mismatch is a config/install problem the user + # must see (previously hidden by a bare `except Exception`). + logger.debug("stale check: server not reachable — connect_to_server will handle") + pass async def ensure_connected() -> "DaemonConnection": diff --git a/tests/test_daemon_manager.py b/tests/test_daemon_manager.py index 6f1d73cd..f54f3b59 100644 --- a/tests/test_daemon_manager.py +++ b/tests/test_daemon_manager.py @@ -164,6 +164,35 @@ def test_server_unreachable_silent(self, mock_connect, mock_src, mock_cfg, tmp_p return_value=str(port_file)): asyncio.run(daemon_manager.check_and_restart_if_stale()) # no raise + @patch("emrg.client.daemon_manager._get_config_mtime", return_value=0.0) + @patch("emrg.client.daemon_manager._get_server_source_mtime", return_value=0.0) + @patch("emrg.client.daemon_manager.connect_to_server", new_callable=AsyncMock) + def test_server_auth_error_propagates(self, mock_connect, mock_src, mock_cfg, tmp_path): + """G129: AuthError (token mismatch) must NOT be swallowed — it's a + config/install problem the user must see, not a transient disconnect.""" + port_file = tmp_path / "emrgd.port" + port_file.write_text("12345\ntoken\n") + mock_connect.side_effect = daemon_manager.AuthError("authentication failed") + + with patch("emrg.client.daemon_manager.get_server_path", + return_value=str(port_file)): + with pytest.raises(daemon_manager.AuthError): + asyncio.run(daemon_manager.check_and_restart_if_stale()) + + @patch("emrg.client.daemon_manager._get_config_mtime", return_value=0.0) + @patch("emrg.client.daemon_manager._get_server_source_mtime", return_value=0.0) + @patch("emrg.client.daemon_manager.connect_to_server", new_callable=AsyncMock) + def test_server_programming_error_propagates(self, mock_connect, mock_src, mock_cfg, tmp_path): + """G129: genuine bugs must surface, not vanish into a bare except Exception.""" + port_file = tmp_path / "emrgd.port" + port_file.write_text("12345\ntoken\n") + mock_connect.side_effect = AttributeError("boom") + + with patch("emrg.client.daemon_manager.get_server_path", + return_value=str(port_file)): + with pytest.raises(AttributeError): + asyncio.run(daemon_manager.check_and_restart_if_stale()) + # ── ensure_connected ─────────────────────────────────────────