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.