From 0120f3a1b364e9aabf687a4729d12c98dc1fe113 Mon Sep 17 00:00:00 2001 From: EMRG Evolution Date: Wed, 5 Aug 2026 16:26:57 +0800 Subject: [PATCH 1/2] emrg: daemon start timeout shows emrgd.log tail (real error, not generic message) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rant 2026-08-05T15:54:28 关联:config.toml 解析错误(如 vision = trues)时 daemon 启动失败,但 CLI 只显示 "failed to start within timeout",吞掉真实报错 (stderr 被 DEVNULL 丢弃),用户无法诊断。 修复:start_daemon 超时后读取 ~/.emrg/emrgd.log 尾部 15 行(R124), 拼接到 RuntimeError 消息中。新增 _read_log_tail helper(utf-8 errors=replace, 文件缺失/读取失败返回空串,不影响原有行为)。 验证:466 passed + import OK + --help OK + _read_log_tail 单测 (正常尾部截取 / 缺失文件返回空)。 --- emrg/client/daemon_manager.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/emrg/client/daemon_manager.py b/emrg/client/daemon_manager.py index 7dcfa642..4e6709fe 100644 --- a/emrg/client/daemon_manager.py +++ b/emrg/client/daemon_manager.py @@ -80,7 +80,22 @@ async def start_daemon() -> subprocess.Popen: if is_running(): logger.info("emrgd started (pid=%d)", proc.pid) return proc - raise RuntimeError("emrgd failed to start within timeout") + # R124: 超时后读取 emrgd.log 尾部打印真实失败原因(rant 2026-08-05T15:54:28 关联: + # config.toml 解析错误时 CLI 只显示 'failed to start within timeout',吞掉真实报错) + tail = _read_log_tail(Path.home() / ".emrg" / "emrgd.log", lines=15) + detail = f"\n emrgd.log 尾部:\n{tail}" if tail else "" + raise RuntimeError(f"emrgd failed to start within timeout{detail}") + + +def _read_log_tail(path: Path, lines: int = 15) -> str: + """Return the last `lines` of a log file (empty string on any error).""" + try: + if not path.exists(): + return "" + data = path.read_text(encoding="utf-8", errors="replace") + return "\n".join(data.rstrip().splitlines()[-lines:]) + except OSError: + return "" async def check_and_restart_if_stale() -> None: From a249e95dbc3dc2fdab0e24582b443ca9219475e7 Mon Sep 17 00:00:00 2001 From: EMRG Evolution Date: Wed, 5 Aug 2026 16:30:42 +0800 Subject: [PATCH 2/2] emrg: add unit tests for _read_log_tail (R124 daemon timeout diagnostics) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 补充技术反馈建议的 3 类测试: 1. tail 截取最后 N 行(30 行 → 取 5) 2. 缺失文件返回空串 3. 文件短于 lines 返回全部 + 非法 UTF-8 用 replacement 字符 25 passed(原 21 + 新增 4)。 --- tests/test_daemon_manager.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/test_daemon_manager.py b/tests/test_daemon_manager.py index cfc625d3..10eec195 100644 --- a/tests/test_daemon_manager.py +++ b/tests/test_daemon_manager.py @@ -287,3 +287,26 @@ def test_close_calls_ws_close(self): conn = self._conn() asyncio.run(conn.close()) assert conn._ws.closed is True + +class TestReadLogTail: + """_read_log_tail — daemon start-timeout diagnostics (R124).""" + + def test_tail_last_lines(self, tmp_path): + p = tmp_path / "emrgd.log" + p.write_text("\n".join(f"line{i}" for i in range(1, 31)), encoding="utf-8") + out = daemon_manager._read_log_tail(p, lines=5) + assert out == "line26\nline27\nline28\nline29\nline30" + + def test_missing_file_returns_empty(self, tmp_path): + assert daemon_manager._read_log_tail(tmp_path / "nope.log") == "" + + def test_shorter_than_lines_returns_all(self, tmp_path): + p = tmp_path / "emrgd.log" + p.write_text("a\nb", encoding="utf-8") + assert daemon_manager._read_log_tail(p, lines=10) == "a\nb" + + def test_invalid_utf8_replaced(self, tmp_path): + p = tmp_path / "emrgd.log" + p.write_bytes(b"ok\n\xff\xfebad\nend") + out = daemon_manager._read_log_tail(p, lines=5) + assert "end" in out and "\ufffd" in out