Skip to content

Commit 3f3e69c

Browse files
fix: bound error_log fetches with paginated journald windows (#2290)
* fix: bound error_log fetches with paginated journald windows (#2279) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AgVydTVw2uzQqdnxZ7jfJv * fix: harden log routes per review round CodeQL allowlist for the split's cross-module regex; overall deadlines on the hassio-proxy addon/system-service log routes; raise-limit hint for matches a terminal window's limit slice left unreturned; module-filter compat expansion for the tools_utility split; non-dict system_log records guarded in filters; app (add-on) wording in agent-facing log text. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AgVydTVw2uzQqdnxZ7jfJv * fix: cover max-limit hint edge and invalid end_time validation Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AgVydTVw2uzQqdnxZ7jfJv * fix: widen the has_more probe to a block compare A one-entry probe compared one rendered line, which a duplicated timestamp-less boundary line could false-match into end-of-history. The 8-entry block probe requires that many consecutive identical lines to straddle the boundary, and a false stop there skips only more of the same duplicates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AgVydTVw2uzQqdnxZ7jfJv * fix: name the embedded server correctly in the restart flow The settings UI relabeled the restart button for the embedded (custom component) server but the wait/give-up copy still said app (add-on) throughout (#2279 feedback). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AgVydTVw2uzQqdnxZ7jfJv * docs: record the Supervisor HTTP transport boundary Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AgVydTVw2uzQqdnxZ7jfJv * fix: compare shared-length prefixes in the has_more probe With lines < probe depth and an overshot offset, both requests clamp and the probe outgrows the window; a full-length compare read that as more history forever. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AgVydTVw2uzQqdnxZ7jfJv * fix: give the embedded restart poll a signal that actually flips instance_id is per-process and an embedded restart reloads the config entry inside the surviving HA process, so the poll timed out after every successful reload. worker_id is pinned to the server instance and flips exactly when the reload completes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AgVydTVw2uzQqdnxZ7jfJv * style: ruff format after check --fix unquoted the annotation Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AgVydTVw2uzQqdnxZ7jfJv * fix: carry the restart mode in the cross-tab broadcast A receiving tab's listener can fire before its own init classifies the deployment, comparing an embedded worker_id baseline against instance_id and timing out. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AgVydTVw2uzQqdnxZ7jfJv * chore: regenerate tool catalog for the reworded docstring Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AgVydTVw2uzQqdnxZ7jfJv --------- Co-authored-by: kingpanther13 <kingpanther13@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent c29ab1f commit 3f3e69c

25 files changed

Lines changed: 3308 additions & 1504 deletions

SECURITY.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@ secrecy and is designed for loopback HTTP or LAN HTTP with a high-entropy
3939
`MCP_SECRET_PATH`. Any peer that can reach the configured path is treated as
4040
trusted — securing the local network is outside ha-mcp's scope.
4141

42+
On app (add-on) installs, ha-mcp additionally talks to the Supervisor REST
43+
API at `http://supervisor` with the Supervisor-issued token. That transport
44+
is the platform's contract, not a choice this project can harden: Supervisor
45+
serves its API over plain HTTP only (`web.TCPSite(..., port=80)` in
46+
`supervisor/api/__init__.py` — no TLS endpoint, no IPC socket), on the
47+
internal `hassio` docker network that is not reachable from the LAN. The
48+
isolation of that internal network is the boundary protecting the token, and
49+
it is enforced by the Home Assistant OS platform, not by ha-mcp.
50+
4251
For internet-facing deployments use the OAuth entrypoint (`ha-mcp-oauth`) or,
4352
for gating access behind an external identity provider instead of per-user HA
4453
tokens, the OIDC entrypoint (`ha-mcp-oidc`; see [docs/oidc.md](docs/oidc.md)),

scripts/codeql_quality_gate.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,16 @@
187187
"default. CodeQL's single-file analysis misses the cross-module "
188188
"import, so the declaration looks dead.",
189189
),
190+
(
191+
"py/unused-global-variable",
192+
"src/ha_mcp/tools/log_common.py",
193+
"_LOG_LEVEL_RE",
194+
"",
195+
"Cross-module use: log_common.py is the leaf module of the tools_utility "
196+
"split, so this regex is imported and read by log_sources.py's "
197+
"_build_raw_error_log level filter. CodeQL's single-file analysis misses "
198+
"the cross-module import, so the declaration looks dead.",
199+
),
190200
(
191201
"py/unused-import",
192202
"packaging/binary/pyinstaller_hooks/runtime_hook.py",

site/src/data/tools.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2100,7 +2100,7 @@
21002100
{
21012101
"name": "ha_get_logs",
21022102
"title": "Get Logs",
2103-
"description": "Get Home Assistant logs from various sources.\n\n**Sources:**\n- \"logbook\" (default): Entity state change history with pagination\n- \"system\": Structured system log entries (errors, warnings) via system_log/list\n- \"error_log\": Raw log text (home-assistant.log on container/pip installs; HA Core's journald stream on Supervisor-backed installs)\n- \"supervisor\": Add-on container logs (requires slug = add-on slug)\n- \"system_service\": HA-Supervisor-managed system service logs (requires\n slug ∈ {supervisor, host, core, dns, audio, cli, multicast, observer})\n- \"logger\": Effective log level per integration via logger/log_info (confirms logger.set_level changes took effect)\n\n**Prefer source='system' for triage.** It returns HA's own deduplicated\nsystem_log entries with counts, first_occurred and full tracebacks; of\nthose only the tracebacks are unrecoverable from the structured\nerror_log summary — they are present in the raw text, so structured=False\ngets them back. Its counts also run\nsince each error first occurred, while structured error_log counts only\nwhat is inside the fetched window (reported as window_start/window_end;\nSupervisor-backed installs read a capped journald slice). Use error_log\nwith structured=True for entries below system_log's WARNING+ ~50-entry\ncap, or for the per-component rollup.\n\n**Shared params:** limit, search (keyword filter on entries/lines; matches integration domain for source='logger')\n**Order:** order='newest' (default) returns most-recent first; order='oldest' returns chronological-first. Applies to all time-ordered sources (logbook, system, error_log, supervisor, system_service); ignored for source='logger' and for error_log with structured=True. For raw-text sources (error_log, supervisor, system_service) it sets the read direction of the most-recent window.\n**Logbook params:** hours_back, entity_id, end_time, offset, compact (default True — strips attribute dicts to save context)\n**System/error_log params:** level (ERROR, WARNING, INFO, DEBUG, CRITICAL)\n**error_log params:** structured, top_n. In structured mode `search`\n matches the message and logger name only, whereas on the raw path it\n matches the whole line; `limit`/`order` do not apply, and issues are\n ranked by count, then severity, then recency.\n**Supervisor params:** slug = add-on slug, e.g. \"core_mosquitto\" (use\n ha_get_app() to list installed slugs)\n**System-service params:** slug = service name. The slug \"supervisor\"\n here means the Supervisor service's own logs, NOT an add-on with\n that name — the source param disambiguates.",
2103+
"description": "Get Home Assistant logs from various sources.\n\n**Sources:**\n- \"logbook\" (default): Entity state change history with pagination\n- \"system\": Structured system log entries (errors, warnings) via system_log/list\n- \"error_log\": Raw log text (home-assistant.log on container/pip installs; HA Core's journald stream on Supervisor-backed installs)\n- \"supervisor\": App (add-on) container logs (requires slug = app slug)\n- \"system_service\": HA-Supervisor-managed system service logs (requires\n slug ∈ {supervisor, host, core, dns, audio, cli, multicast, observer})\n- \"logger\": Effective log level per integration via logger/log_info (confirms logger.set_level changes took effect)\n\n**Prefer source='system' for triage.** It returns HA's own deduplicated\nsystem_log entries with counts, first_occurred and full tracebacks; of\nthose only the tracebacks are unrecoverable from the structured\nerror_log summary — they are present in the raw text, so structured=False\ngets them back. Its counts also run\nsince each error first occurred, while structured error_log counts only\nwhat is inside the fetched window (reported as window_start/window_end;\nevery install now reads a capped window). Use error_log\nwith structured=True for entries below system_log's WARNING+ ~50-entry\ncap, or for the per-component rollup.\n\n**Shared params:** limit, search (keyword filter on entries/lines; matches integration domain for source='logger')\n**Order:** order='newest' (default) returns most-recent first; order='oldest' returns chronological-first. Applies to all time-ordered sources (logbook, system, error_log, supervisor, system_service); ignored for source='logger' and for error_log with structured=True. For raw-text sources (error_log, supervisor, system_service) it sets the read direction of the most-recent window.\n**Logbook params:** hours_back, entity_id, end_time, compact (default True — strips attribute dicts to save context)\n**Pagination (logbook + error_log):** offset pages deeper; ignored for the\n other sources. Logbook responses carry has_more plus a\n pagination_hint. On error_log, offset counts raw log lines back from\n the newest entry (journald entries on Supervisor-backed installs),\n both modes read a bounded window per call — so `level`/`search`\n filter and `limit` slice within that window only, and window_lines\n reports the size actually requested — and the response carries\n has_more with a next_offset to pass back while it stays true.\n**System/error_log params:** level (ERROR, WARNING, INFO, DEBUG, CRITICAL)\n**error_log params:** structured, top_n. In structured mode `search`\n matches the message and logger name only, whereas on the raw path it\n matches the whole line; `limit`/`order` do not apply, issues are\n ranked by count, then severity, then recency, and the summary covers\n a fixed deep window rather than the caller's limit.\n**Supervisor params:** slug = app slug, e.g. \"core_mosquitto\" (use\n ha_get_app() to list installed slugs)\n**System-service params:** slug = service name. The slug \"supervisor\"\n here means the Supervisor service's own logs, NOT an app with\n that name — the source param disambiguates.",
21042104
"inputSchema": {
21052105
"properties": {
21062106
"source": {
@@ -2132,7 +2132,7 @@
21322132
"default": null
21332133
},
21342134
"offset": {
2135-
"type": "Annotated[int, Field(ge=0)]",
2135+
"type": "Annotated[int, Field(ge=0, description=\"Page deeper into source='logbook' and source='error_log' (ignored for other sources). On error_log it counts raw log lines back from the newest entry; pass the response's 'next_offset' to continue while 'has_more' is true.\")]",
21362136
"default": 0
21372137
},
21382138
"compact": {
@@ -2165,7 +2165,7 @@
21652165
"tags": [
21662166
"History & Statistics"
21672167
],
2168-
"source_file": "tools_utility.py"
2168+
"source_file": "tools_logs.py"
21692169
},
21702170
{
21712171
"name": "ha_get_integration",

0 commit comments

Comments
 (0)