Files
hermes-agent/hermes_cli/subcommands/logs.py
Halldrix 60f9d2abb1 fix(debug-share): capture update.log and desktop-update-handoff.log
When a Desktop-driven update fails at the Electron rebuild (updater
exit 6, 'Code and dependencies updated, but the Desktop app REBUILD
FAILED'), the only artifacts holding the root cause are
~/.hermes/logs/update.log (full stdout/stderr mirror of hermes update,
written by _UpdateOutputStream) and ~/.hermes/logs/desktop-update-handoff.log
(hand-off stages including the 'desktop --force-build --build-only' retry).
Both the updater's failure messages and the Desktop error box direct
users to 'hermes debug share', but the share captured neither file:
LOG_FILES and the debug.py capture chain covered only the classic
five logs, so update-failure issues (e.g. #100874) arrived with the
diagnostics that matter most missing.

Additionally, 'hermes logs list' (a directory scan) already listed
both files while LOG_FILES had no readable key for them, so
'hermes logs update' failed with 'Unknown log' against a file the
CLI itself displays.

Wire both files through the whole chain:
- hermes_cli/logs.py: add 'update' and 'handoff' LOG_FILES keys
  (fixes list-vs-read inconsistency; tail/follow/filters come free).
- hermes_cli/debug.py: _capture_default_log_snapshots,
  collect_debug_report (512 KB snapshot cap and the 100-line tail
  budget both apply; a hand-built snapshot dict without the new keys
  degrades to the old five-section report instead of KeyError),
  collect_share_bundle full logs, the paste-upload label list in
  build_debug_share, and the --local print list. The --nous bundle
  and privacy notice inherit the new keys; force-redaction applies
  via _capture_log_snapshot as for every other log.
- hermes_cli/subcommands/logs.py + website docs: name the new logs.

Tests (tests/hermes_cli/test_debug_share_update_logs.py): 12 new
tests — snapshot keys present with real content, missing files
degrade to the honest note without raising, report sections appear,
bundle keys omitted when absent, upload/--local label lists include
the new logs, LOG_FILES keys resolve, tail_log end-to-end. Sabotage
pass: 10 of 12 fail without the fix.
2026-09-29 21:48:25 -05:00

53 lines
2.6 KiB
Python

"""``hermes logs`` subcommand parser."""
from __future__ import annotations
import argparse
from typing import Callable
def build_logs_parser(subparsers, *, cmd_logs: Callable) -> None:
"""Attach the ``logs`` subcommand to ``subparsers``."""
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 / desktop.log / update.log / desktop-update-handoff.log",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""\
Examples:
hermes logs Show last 50 lines of agent.log
hermes logs -f Follow agent.log in real time
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 update Show last 50 lines of update.log (hermes update mirror)
hermes logs handoff Show last 50 lines of desktop-update-handoff.log
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
hermes logs --since 1h Lines from the last hour
hermes logs --since 30m -f Follow, starting from 30 min ago
hermes logs list List available log files with sizes
""")
logs_parser.add_argument(
"log_name",
nargs="?",
default="agent",
help="Log to view: agent (default), errors, gateway, gui, desktop, update, handoff, or 'list' to show available files",
)
logs_parser.add_argument(
"-n", "--lines", type=int, default=50, help="Number of lines to show (default: 50)")
logs_parser.add_argument(
"-f", "--follow", action="store_true", help="Follow the log in real time (like tail -f)")
logs_parser.add_argument(
"--level", metavar="LEVEL", help="Minimum log level to show (DEBUG, INFO, WARNING, ERROR)")
logs_parser.add_argument(
"--session", metavar="ID", help="Filter lines containing this session ID substring")
logs_parser.add_argument(
"--since", metavar="TIME", help="Show lines since TIME ago (e.g. 1h, 30m, 2d)")
logs_parser.add_argument(
"--component", metavar="NAME",
help="Filter by component: gateway, agent, tools, cli, cron, gui")
logs_parser.set_defaults(func=cmd_logs)