fix(debug): include gui.log (dashboard/TUI/pty/websocket) in hermes debug share

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).
This commit is contained in:
kshitijk4poor 2026-06-19 16:07:47 +05:30 • committed by Teknium
parent ddca590cac
commit 01a6f11896
3 changed files with 47 additions and 11 deletions

View file

@ -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:

View file

@ -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

View file

@ -734,7 +734,7 @@ Upload a debug report (system info + recent logs) to a paste service and get a s
| `--expire <days>` | 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.