diff --git a/hermes_cli/debug.py b/hermes_cli/debug.py index b5a7852aee..0404e78371 100644 --- a/hermes_cli/debug.py +++ b/hermes_cli/debug.py @@ -372,6 +372,35 @@ def _primary_log_path(log_name: str) -> Optional[Path]: return (get_hermes_home() / "logs" / filename) if filename else None +# Logs written by a client process rather than by this backend. When the +# desktop app talks to a remote/docker/SSH backend, `hermes debug share` runs +# on the *backend* and can never see them — a bare "(file not found)" then +# reads as "the app logged nothing" and sends triage down a dead end, which is +# exactly the wrong answer when the client is the thing being debugged. +_CLIENT_SIDE_LOGS = { + "desktop": ( + "written by Hermes Desktop on the machine running the app, not by this " + "backend. If the desktop connects to a remote/docker/SSH backend, collect " + "it on that client machine" + ), +} + + +def _missing_log_note(log_name: str) -> str: + """Explain a missing log instead of stating a bare absence. + + For a client-side log the absence is expected on a remote backend, so the + note names the writer and the path to collect by hand. + """ + reason = _CLIENT_SIDE_LOGS.get(log_name) + if reason is None: + return "(file not found)" + + primary = _primary_log_path(log_name) + where = f" — expected at {primary}" if primary else "" + return f"(not on this host: {reason}{where})" + + def _resolve_log_path(log_name: str) -> Optional[Path]: """Find the log file for *log_name*, falling back to the .1 rotation. @@ -432,7 +461,11 @@ def _capture_log_snapshot( log_path = _resolve_log_path(log_name) if log_path is None: primary = _primary_log_path(log_name) - tail = "(file empty)" if primary and primary.exists() else "(file not found)" + tail = ( + "(file empty)" + if primary and primary.exists() + else _missing_log_note(log_name) + ) return LogSnapshot(path=None, tail_text=tail, full_text=None) try: diff --git a/tests/hermes_cli/test_debug.py b/tests/hermes_cli/test_debug.py index 415aea7b97..2a3d0206b1 100644 --- a/tests/hermes_cli/test_debug.py +++ b/tests/hermes_cli/test_debug.py @@ -146,6 +146,63 @@ class TestCaptureLogSnapshot: assert len(kept) == 10 +class TestMissingLogNote: + """A missing log explains itself when the writer isn't this backend. + + `hermes debug share` runs on the backend, so a desktop connected to a + remote/docker/SSH backend can never contribute desktop.log. Reporting a + bare absence sends triage after a client-side bug it cannot see. + """ + + def test_backend_written_log_reports_plain_absence(self, hermes_home): + from hermes_cli.debug import _capture_log_snapshot + + (hermes_home / "logs" / "agent.log").unlink() + + snap = _capture_log_snapshot("agent", tail_lines=10) + assert snap.full_text is None + assert snap.tail_text == "(file not found)" + + def test_client_written_log_names_its_writer_and_path(self, hermes_home): + from hermes_cli.debug import _capture_log_snapshot + + (hermes_home / "logs" / "desktop.log").unlink() + + snap = _capture_log_snapshot("desktop", tail_lines=10) + assert snap.full_text is None + assert "not on this host" in snap.tail_text + assert "Hermes Desktop" in snap.tail_text + # The reader needs the path to collect by hand on the client machine. + assert str(hermes_home / "logs" / "desktop.log") in snap.tail_text + + def test_present_client_log_is_captured_normally(self, hermes_home): + """A local backend still reads desktop.log — the note is only for a miss.""" + from hermes_cli.debug import _capture_log_snapshot + + snap = _capture_log_snapshot("desktop", tail_lines=10) + assert "backend spawned" in snap.tail_text + assert "not on this host" not in snap.tail_text + + def test_empty_client_log_is_empty_not_absent(self, hermes_home): + """An empty file means the app ran and logged nothing — a different fact.""" + from hermes_cli.debug import _capture_log_snapshot + + (hermes_home / "logs" / "desktop.log").write_text("") + + snap = _capture_log_snapshot("desktop", tail_lines=10) + assert snap.tail_text == "(file empty)" + + def test_report_carries_the_note_for_a_remote_backend(self, hermes_home): + """The uploaded report — what people paste into support — must explain it.""" + from hermes_cli.debug import collect_debug_report + + (hermes_home / "logs" / "desktop.log").unlink() + + report = collect_debug_report(log_lines=10, dump_text="dump\n") + assert "--- desktop.log" in report + assert "not on this host" in report + + # ---------------------------------------------------------------------------