From e1f1c8a935ea8d8318726e78face5197c026c8ab Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Thu, 10 Sep 2026 01:39:20 -0700 Subject: [PATCH] fix(dashboard): stopped gateway no longer reports a dead process's platform error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gateway_state.json preserves per-platform entries across restarts, so a gateway that once ran without TELEGRAM_BOT_TOKEN and then stopped kept reporting 'fatal / No bot token configured' on the Channels/Messaging pages even after the user saved a token — the Desktop showed 'Saved' on both fields next to the error. Only a live gateway's verdict describes current config; when no gateway is running the platform reads gateway_stopped with no error. --- hermes_cli/web_routers/messaging.py | 6 ++ ...test_web_server_messaging_stale_runtime.py | 55 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 tests/hermes_cli/test_web_server_messaging_stale_runtime.py diff --git a/hermes_cli/web_routers/messaging.py b/hermes_cli/web_routers/messaging.py index b0fcb43010..e0299c69a1 100644 --- a/hermes_cli/web_routers/messaging.py +++ b/hermes_cli/web_routers/messaging.py @@ -214,6 +214,12 @@ def _messaging_platform_payload( pid_probe=get_running_pid_cached, runtime_reader=read_runtime_status, runtime_pid_probe=get_runtime_status_running_pid, ).running + if not gateway_running: + # gateway_state.json outlives its writer and keeps per-platform entries across + # restarts, so a stopped gateway that once ran WITHOUT credentials still says + # "fatal / No bot token configured" after the user saved a token. Only a live + # process's verdict describes the current config; a dead one's is history. + runtime_platform = {} def env_value(key: str) -> str: # Profile-scoped: judge only the profile's own .env — the dashboard process's diff --git a/tests/hermes_cli/test_web_server_messaging_stale_runtime.py b/tests/hermes_cli/test_web_server_messaging_stale_runtime.py new file mode 100644 index 0000000000..49ec280117 --- /dev/null +++ b/tests/hermes_cli/test_web_server_messaging_stale_runtime.py @@ -0,0 +1,55 @@ +"""A stopped gateway's per-platform verdict is history, not current state. + +``gateway_state.json`` preserves platform entries across restarts, so a gateway that once ran +WITHOUT a Telegram token and then stopped leaves ``fatal / No bot token configured`` behind. The +Channels payload must not repeat that after the user saved credentials: with no live gateway the +platform reads ``gateway_stopped`` and carries no error (Desktop Messaging page report). +""" +import json + +import pytest + + +_VALID_BOT_TOKEN = "123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ_1234" + + +@pytest.fixture +def client(monkeypatch, _isolate_hermes_home): + try: + from starlette.testclient import TestClient + except ImportError: + pytest.skip("fastapi/starlette not installed") + + import hermes_state + from hermes_constants import get_hermes_home + from hermes_cli.web_server import app, _SESSION_HEADER_NAME, _SESSION_TOKEN + + home = get_hermes_home() + monkeypatch.setattr(hermes_state, "DEFAULT_DB_PATH", home / "state.db") + monkeypatch.delenv("TELEGRAM_BOT_TOKEN", raising=False) + (home / ".env").write_text(f"TELEGRAM_BOT_TOKEN={_VALID_BOT_TOKEN}\nTELEGRAM_ALLOWED_USERS=42\n", encoding="utf-8") + (home / "config.yaml").write_text("platforms:\n telegram:\n enabled: true\n", encoding="utf-8") + (home / "gateway_state.json").write_text(json.dumps({ + "kind": "gateway", "pid": 999_999_999, "start_time": 1.0, "gateway_state": "stopped", + "exit_reason": "shutdown", "updated_at": "2026-01-01T00:00:00+00:00", + "platforms": {"telegram": { + "state": "fatal", "error_code": "missing_credentials", + "error_message": "No bot token configured", + "writer_pid": 999_999_999, "writer_start_time": 1.0, + }}, + }), encoding="utf-8") + c = TestClient(app) + c.headers[_SESSION_HEADER_NAME] = _SESSION_TOKEN + return c + + +def test_stopped_gateway_does_not_report_stale_platform_error(client): + payload = client.get("/api/messaging/platforms").json() + telegram = next(p for p in payload["platforms"] if p["id"] == "telegram") + + assert telegram["configured"] is True + assert telegram["gateway_running"] is False + # The saved token is current; the dead gateway's "no token" verdict is not. + assert telegram["state"] == "gateway_stopped" + assert telegram["error_code"] is None + assert telegram["error_message"] is None