From 1b302a04746d46808cbe76bd46511744416460b3 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Wed, 3 Jun 2026 05:41:35 -0700 Subject: [PATCH] feat(debug): include desktop.log in hermes debug share / /debug / hermes logs (#38203) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Electron desktop app writes boot failures, backend spawn output, and Python tracebacks to HERMES_HOME/logs/desktop.log, but debug-share only captured agent/errors/gateway — so desktop boot issues never made it into shared debug reports. - logs.py: register desktop -> desktop.log (enables 'hermes logs desktop') - debug.py: capture desktop snapshot, add to summary report, upload full desktop.log in 'share', update privacy notice - gateway /debug inherits the desktop tail via collect_debug_report() - main.py + docs: help text and log-name table (also adds missing gui row) - tests: desktop seed in fixture, new report test, three_pastes -> four_pastes --- hermes_cli/debug.py | 32 ++++++++++++++++++++++---- hermes_cli/logs.py | 2 ++ hermes_cli/main.py | 3 ++- tests/hermes_cli/test_debug.py | 25 ++++++++++++++++---- website/docs/reference/cli-commands.md | 2 ++ 5 files changed, 55 insertions(+), 9 deletions(-) diff --git a/hermes_cli/debug.py b/hermes_cli/debug.py index e3f2306f6..78ba31983 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 — may contain - conversation fragments and file paths) - • Full agent.log and gateway.log (up to 512 KB each — likely contains - conversation content, tool outputs, and file paths) + • 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) 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 ), + "desktop": _capture_log_snapshot( + "desktop", tail_lines=errors_lines, redact=redact + ), } @@ -569,6 +572,10 @@ def collect_debug_report( buf.write(f"--- gateway.log (last {errors_lines} lines) ---\n") buf.write(log_snapshots["gateway"].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") return buf.getvalue() @@ -611,12 +618,15 @@ def run_debug_share(args): ) agent_log = log_snapshots["agent"].full_text gateway_log = log_snapshots["gateway"].full_text + desktop_log = log_snapshots["desktop"].full_text # Prepend dump header to each full log so every paste is self-contained. 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 desktop_log: + desktop_log = dump_text + "\n\n--- full desktop.log ---\n" + desktop_log # Visible banner so reviewers reading the public paste know redaction # was applied at upload time. Banner is omitted under --no-redact. @@ -626,6 +636,8 @@ def run_debug_share(args): agent_log = _REDACTION_BANNER + agent_log if gateway_log: gateway_log = _REDACTION_BANNER + gateway_log + if desktop_log: + desktop_log = _REDACTION_BANNER + desktop_log if local_only: print(report) @@ -639,6 +651,11 @@ def run_debug_share(args): print("FULL gateway.log") print(f"{'=' * 60}\n") print(gateway_log) + if desktop_log: + print(f"\n\n{'=' * 60}") + print("FULL desktop.log") + print(f"{'=' * 60}\n") + print(desktop_log) return print("Uploading...") @@ -668,6 +685,13 @@ def run_debug_share(args): except Exception as exc: failures.append(f"gateway.log: {exc}") + # 4. Full desktop.log (optional — Electron app boot + backend output) + if desktop_log: + try: + urls["desktop.log"] = upload_to_pastebin(desktop_log, expiry_days=expiry) + except Exception as exc: + failures.append(f"desktop.log: {exc}") + # Print results label_width = max(len(k) for k in urls) print(f"\nDebug report uploaded:") diff --git a/hermes_cli/logs.py b/hermes_cli/logs.py index d580751b4..220051f73 100644 --- a/hermes_cli/logs.py +++ b/hermes_cli/logs.py @@ -11,6 +11,7 @@ Usage examples:: hermes logs errors # last 50 lines of errors.log hermes logs gateway -n 100 # last 100 lines of gateway.log hermes logs gui -f # follow gui.log (dashboard/pty/ws) + hermes logs desktop -f # follow desktop.log (Electron app boot/backend) hermes logs --level WARNING # only WARNING+ lines hermes logs --session abc123 # filter by session ID substring hermes logs --component tools # only tool-related lines @@ -33,6 +34,7 @@ LOG_FILES = { "errors": "errors.log", "gateway": "gateway.log", "gui": "gui.log", + "desktop": "desktop.log", } # Log line timestamp regex — matches "2026-04-05 22:35:00,123" or diff --git a/hermes_cli/main.py b/hermes_cli/main.py index 9320db4d3..ae83d7546 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -15192,7 +15192,7 @@ Examples: logs_parser = subparsers.add_parser( "logs", help="View and filter Hermes log files", - description="View, tail, and filter agent.log / errors.log / gateway.log / gui.log", + description="View, tail, and filter agent.log / errors.log / gateway.log / gui.log / desktop.log", formatter_class=argparse.RawDescriptionHelpFormatter, epilog="""\ Examples: @@ -15201,6 +15201,7 @@ Examples: hermes logs errors Show last 50 lines of errors.log hermes logs gateway -n 100 Show last 100 lines of gateway.log hermes logs gui -f Follow gui.log in real time + hermes logs desktop -f Follow desktop.log (Electron app boot/backend) hermes logs --level WARNING Only show WARNING and above hermes logs --session abc123 Filter by session ID hermes logs --component tools Only show tool-related lines diff --git a/tests/hermes_cli/test_debug.py b/tests/hermes_cli/test_debug.py index b3ce60de2..427f90655 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 / "desktop.log").write_text( + "2026-04-12 17:00:15 INFO desktop: backend spawned\n" + ) return home @@ -451,6 +454,15 @@ class TestCollectDebugReport: assert "--- gateway.log" in report + def test_report_includes_desktop_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 "--- desktop.log" in report + assert "backend spawned" in report + def test_missing_logs_handled(self, tmp_path, monkeypatch): home = tmp_path / ".hermes" home.mkdir() @@ -526,8 +538,8 @@ class TestRunDebugShare: assert "FULL agent.log" in out assert "FULL gateway.log" in out - def test_share_uploads_three_pastes(self, hermes_home, capsys): - """Successful share uploads report + agent.log + gateway.log.""" + def test_share_uploads_four_pastes(self, hermes_home, capsys): + """Successful share uploads report + agent.log + gateway.log + desktop.log.""" from hermes_cli.debug import run_debug_share args = MagicMock() @@ -549,14 +561,16 @@ class TestRunDebugShare: run_debug_share(args) out = capsys.readouterr().out - # Should have 3 uploads: report, agent.log, gateway.log - assert call_count[0] == 3 + # Should have 4 uploads: report, agent.log, gateway.log, desktop.log + assert call_count[0] == 4 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 "Report" in out assert "agent.log" in out assert "gateway.log" in out + assert "desktop.log" in out # Each log paste should start with the dump header agent_paste = uploaded_content[1] @@ -565,6 +579,9 @@ 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] + assert "--- hermes dump ---" in desktop_paste + assert "--- full desktop.log ---" in desktop_paste def test_share_keeps_report_and_full_log_on_same_snapshot(self, hermes_home, capsys): """A mid-run rotation must not make full agent.log older than the report.""" diff --git a/website/docs/reference/cli-commands.md b/website/docs/reference/cli-commands.md index e9bfc9b78..593d32e5c 100644 --- a/website/docs/reference/cli-commands.md +++ b/website/docs/reference/cli-commands.md @@ -835,6 +835,8 @@ View, tail, and filter Hermes log files. All logs are stored in `~/.hermes/logs/ | `agent` (default) | `agent.log` | All agent activity — API calls, tool dispatch, session lifecycle (INFO and above) | | `errors` | `errors.log` | Warnings and errors only — a filtered subset of agent.log | | `gateway` | `gateway.log` | Messaging gateway activity — platform connections, message dispatch, webhook events | +| `gui` | `gui.log` | Dashboard / TUI-gateway / PTY-bridge / websocket events | +| `desktop` | `desktop.log` | Electron desktop app — boot, backend spawn output, and recent Python tracebacks | ### Options