Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Agent.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 路径不受影响)
Expand Down
2 changes: 1 addition & 1 deletion README.cn.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 即失败

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -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

Expand Down
12 changes: 10 additions & 2 deletions emrg/client/daemon_manager.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -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__)

Expand DownExpand Up@@ -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":
Expand Down
29 changes: 29 additions & 0 deletions tests/test_daemon_manager.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 ─────────────────────────────────────────

Expand Down
Loading