From 01a6f11896673764a97fd51a5a36dfc73e8ab0b9 Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Fri, 19 Jun 2026 16:07:47 +0530 Subject: [PATCH] fix(debug): include gui.log (dashboard/TUI/pty/websocket) in hermes debug share MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gui.log was registered in hermes_cli/logs.py::LOG_FILES (and surfaced by `hermes logs gui`) but was never wired into `hermes debug share`. The share report captured agent/errors/gateway/desktop tails plus full agent/gateway/ desktop logs — but nothing from gui.log, the surface the dashboard, TUI-over- PTY bridge, and websocket layer (hermes_cli.web_server / pty_bridge / tui_gateway) actually write to. A user reporting a dashboard or TUI bug shared zero breadcrumbs from the broken surface. Wire gui.log through all three share surfaces, matching the existing pattern: - _capture_default_log_snapshots(): capture the gui snapshot (redacted like the rest) - collect_debug_report(): add the gui.log summary tail block - build_debug_share(): pull gui full_text, prepend dump header + redaction banner, add to the upload loop - run_debug_share() --local branch: same, plus the local print block - _PRIVACY_NOTICE: name gui.log in both bullets Redaction is inherited for free — the gui snapshot goes through the same _capture_log_snapshot(..., redact=redact) path, so secrets are scrubbed in both the tail and full text (verified E2E: seeded key masked by default, passes through under --no-redact, raw token never leaks). Tests: seed gui.log in the fixture, add test_report_includes_gui_log, and bump the upload-count tripwire 4->5 (test_share_uploads_five_pastes). --- hermes_cli/debug.py | 27 ++++++++++++++++++++---- tests/hermes_cli/test_debug.py | 29 ++++++++++++++++++++------ website/docs/reference/cli-commands.md | 2 +- 3 files changed, 47 insertions(+), 11 deletions(-) diff --git a/hermes_cli/debug.py b/hermes_cli/debug.py index 809676d1fc8..e5627f24bf5 100644 --- a/hermes_cli/debug.py +++ b/hermes_cli/debug.py @@ -191,10 +191,10 @@ _PRIVACY_NOTICE = """\ ⚠️ This will upload the following to a public paste service: • System info (OS, Python version, Hermes version, provider, which API keys are configured — NOT the actual keys) - • Recent log lines (agent.log, errors.log, gateway.log, desktop.log — may - contain conversation fragments and file paths) - • Full agent.log, gateway.log, and desktop.log (up to 512 KB each — likely - contains conversation content, tool outputs, and file paths) + • Recent log lines (agent.log, errors.log, gateway.log, gui.log, desktop.log + — may contain conversation fragments and file paths) + • Full agent.log, gateway.log, gui.log, and desktop.log (up to 512 KB each — + likely contains conversation content, tool outputs, and file paths) Pastes auto-delete after 6 hours. """ @@ -503,6 +503,9 @@ def _capture_default_log_snapshots( "gateway": _capture_log_snapshot( "gateway", tail_lines=errors_lines, redact=redact ), + "gui": _capture_log_snapshot( + "gui", tail_lines=errors_lines, redact=redact + ), "desktop": _capture_log_snapshot( "desktop", tail_lines=errors_lines, redact=redact ), @@ -574,6 +577,10 @@ def collect_debug_report( buf.write(log_snapshots["gateway"].tail_text) buf.write("\n\n") + buf.write(f"--- gui.log (last {errors_lines} lines) ---\n") + buf.write(log_snapshots["gui"].tail_text) + buf.write("\n\n") + buf.write(f"--- desktop.log (last {errors_lines} lines) ---\n") buf.write(log_snapshots["desktop"].tail_text) buf.write("\n") @@ -639,6 +646,7 @@ def build_debug_share( ) agent_log = log_snapshots["agent"].full_text gateway_log = log_snapshots["gateway"].full_text + gui_log = log_snapshots["gui"].full_text desktop_log = log_snapshots["desktop"].full_text # Prepend dump header to each full log so every paste is self-contained. @@ -646,6 +654,8 @@ def build_debug_share( agent_log = dump_text + "\n\n--- full agent.log ---\n" + agent_log if gateway_log: gateway_log = dump_text + "\n\n--- full gateway.log ---\n" + gateway_log + if gui_log: + gui_log = dump_text + "\n\n--- full gui.log ---\n" + gui_log if desktop_log: desktop_log = dump_text + "\n\n--- full desktop.log ---\n" + desktop_log @@ -657,6 +667,8 @@ def build_debug_share( agent_log = _REDACTION_BANNER + agent_log if gateway_log: gateway_log = _REDACTION_BANNER + gateway_log + if gui_log: + gui_log = _REDACTION_BANNER + gui_log if desktop_log: desktop_log = _REDACTION_BANNER + desktop_log @@ -670,6 +682,7 @@ def build_debug_share( for label, content in ( ("agent.log", agent_log), ("gateway.log", gateway_log), + ("gui.log", gui_log), ("desktop.log", desktop_log), ): if not content: @@ -712,11 +725,14 @@ def run_debug_share(args): ) agent_log = log_snapshots["agent"].full_text gateway_log = log_snapshots["gateway"].full_text + gui_log = log_snapshots["gui"].full_text desktop_log = log_snapshots["desktop"].full_text if agent_log: agent_log = dump_text + "\n\n--- full agent.log ---\n" + agent_log if gateway_log: gateway_log = dump_text + "\n\n--- full gateway.log ---\n" + gateway_log + if gui_log: + gui_log = dump_text + "\n\n--- full gui.log ---\n" + gui_log if desktop_log: desktop_log = dump_text + "\n\n--- full desktop.log ---\n" + desktop_log if redact: @@ -725,12 +741,15 @@ def run_debug_share(args): agent_log = _REDACTION_BANNER + agent_log if gateway_log: gateway_log = _REDACTION_BANNER + gateway_log + if gui_log: + gui_log = _REDACTION_BANNER + gui_log if desktop_log: desktop_log = _REDACTION_BANNER + desktop_log print(report) for title, body in ( ("FULL agent.log", agent_log), ("FULL gateway.log", gateway_log), + ("FULL gui.log", gui_log), ("FULL desktop.log", desktop_log), ): if body: diff --git a/tests/hermes_cli/test_debug.py b/tests/hermes_cli/test_debug.py index 615e379f7d2..f8d958ffa86 100644 --- a/tests/hermes_cli/test_debug.py +++ b/tests/hermes_cli/test_debug.py @@ -31,6 +31,9 @@ def hermes_home(tmp_path, monkeypatch): (logs_dir / "gateway.log").write_text( "2026-04-12 17:00:10 INFO gateway.run: started\n" ) + (logs_dir / "gui.log").write_text( + "2026-04-12 17:00:12 INFO hermes_cli.web_server: dashboard request\n" + ) (logs_dir / "desktop.log").write_text( "2026-04-12 17:00:15 INFO desktop: backend spawned\n" ) @@ -454,6 +457,15 @@ class TestCollectDebugReport: assert "--- gateway.log" in report + def test_report_includes_gui_log(self, hermes_home): + from hermes_cli.debug import collect_debug_report + + with patch("hermes_cli.dump.run_dump"): + report = collect_debug_report(log_lines=50) + + assert "--- gui.log" in report + assert "dashboard request" in report + def test_report_includes_desktop_log(self, hermes_home): from hermes_cli.debug import collect_debug_report @@ -538,8 +550,8 @@ class TestRunDebugShare: assert "FULL agent.log" in out assert "FULL gateway.log" in out - def test_share_uploads_four_pastes(self, hermes_home, capsys): - """Successful share uploads report + agent.log + gateway.log + desktop.log.""" + def test_share_uploads_five_pastes(self, hermes_home, capsys): + """Successful share uploads report + agent.log + gateway.log + gui.log + desktop.log.""" from hermes_cli.debug import run_debug_share args = MagicMock() @@ -561,15 +573,17 @@ class TestRunDebugShare: run_debug_share(args) out = capsys.readouterr().out - # Should have 4 uploads: report, agent.log, gateway.log, desktop.log - assert call_count[0] == 4 + # Should have 5 uploads: report, agent.log, gateway.log, gui.log, desktop.log + assert call_count[0] == 5 assert "paste.rs/paste1" in out # Report assert "paste.rs/paste2" in out # agent.log assert "paste.rs/paste3" in out # gateway.log - assert "paste.rs/paste4" in out # desktop.log + assert "paste.rs/paste4" in out # gui.log + assert "paste.rs/paste5" in out # desktop.log assert "Report" in out assert "agent.log" in out assert "gateway.log" in out + assert "gui.log" in out assert "desktop.log" in out # Each log paste should start with the dump header @@ -579,7 +593,10 @@ class TestRunDebugShare: gateway_paste = uploaded_content[2] assert "--- hermes dump ---" in gateway_paste assert "--- full gateway.log ---" in gateway_paste - desktop_paste = uploaded_content[3] + gui_paste = uploaded_content[3] + assert "--- hermes dump ---" in gui_paste + assert "--- full gui.log ---" in gui_paste + desktop_paste = uploaded_content[4] assert "--- hermes dump ---" in desktop_paste assert "--- full desktop.log ---" in desktop_paste diff --git a/website/docs/reference/cli-commands.md b/website/docs/reference/cli-commands.md index 3071ac0e5fc..90bc1ef83a6 100644 --- a/website/docs/reference/cli-commands.md +++ b/website/docs/reference/cli-commands.md @@ -734,7 +734,7 @@ Upload a debug report (system info + recent logs) to a paste service and get a s | `--expire ` | Paste expiry in days (default: 7). | | `--local` | Print the report locally instead of uploading. | -The report includes system info (OS, Python version, Hermes version), recent agent and gateway logs (512 KB limit per file), and redacted API key status. Keys are always redacted — no secrets are uploaded. +The report includes system info (OS, Python version, Hermes version), recent agent, gateway, GUI/dashboard, and desktop logs (512 KB limit per file), and redacted API key status. Keys are always redacted — no secrets are uploaded. Paste services tried in order: paste.rs, dpaste.com.