From c0a1b71f5ce58336af8fe72e3fa17b9161824f02 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Wed, 15 Apr 2026 16:56:21 +0200 Subject: [PATCH 1/3] Log unexpected errors in api_process wrappers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `api_process` and `api_process_raw` decorators silently swallowed any `HassioError` that bubbled up from endpoint handlers, returning `"Unknown error, see Supervisor logs"` to the caller while logging nothing. This made the response message actively misleading: e.g. when an endpoint touching D-Bus hit `DBusNotConnectedError` (raised without a message by `@dbus_connected`), Core would surface `SupervisorBadRequestError: Unknown error, see Supervisor logs` and the Supervisor logs would contain no trace of it. Log the caught `HassioError` with traceback before delegating to `api_return_error` so the "see Supervisor logs" hint is actually actionable. The `APIError` branch is left alone — those carry explicit status codes and messages set by Supervisor code and are already visible in the response. Co-Authored-By: Claude Opus 4.6 (1M context) --- supervisor/api/utils.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/supervisor/api/utils.py b/supervisor/api/utils.py index e2f0ce76f85..768b88e87f1 100644 --- a/supervisor/api/utils.py +++ b/supervisor/api/utils.py @@ -3,6 +3,7 @@ import asyncio from collections.abc import Callable, Mapping import json +import logging from typing import Any, cast from aiohttp import web @@ -33,6 +34,8 @@ from ..utils.log_format import format_message from . import const +_LOGGER: logging.Logger = logging.getLogger(__name__) + def extract_supervisor_token(request: web.Request) -> str | None: """Extract Supervisor token from request.""" @@ -72,6 +75,7 @@ async def wrap_api(*args, **kwargs) -> web.Response | web.StreamResponse: err, status=err.status, job_id=err.job_id, headers=err.headers ) except HassioError as err: + _LOGGER.exception("Unexpected error during API call: %s", err) return api_return_error(err) if isinstance(answer, (dict, list)): @@ -119,6 +123,7 @@ async def wrap_api(*args, **kwargs) -> web.Response | web.StreamResponse: job_id=err.job_id, ) except HassioError as err: + _LOGGER.exception("Unexpected error during API call: %s", err) return api_return_error( err, error_type=error_type or const.CONTENT_TYPE_BINARY ) From 84ca80617ccdbbfacd9e939c91c97ef6991719b4 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Thu, 16 Apr 2026 17:19:13 +0200 Subject: [PATCH 2/3] Capture unexpected API errors to Sentry Non-APIError HassioError exceptions reaching api_process indicate missing error handling in the endpoint handler. In addition to the logging added in the previous commit, also send these to Sentry so they surface as actionable issues rather than silently returning "Unknown error, see Supervisor logs" to the caller. --- supervisor/api/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/supervisor/api/utils.py b/supervisor/api/utils.py index 768b88e87f1..ccbe52a363e 100644 --- a/supervisor/api/utils.py +++ b/supervisor/api/utils.py @@ -32,6 +32,7 @@ from ..utils import check_exception_chain, get_message_from_exception_chain from ..utils.json import json_dumps, json_loads as json_loads_util from ..utils.log_format import format_message +from ..utils.sentry import async_capture_exception from . import const _LOGGER: logging.Logger = logging.getLogger(__name__) @@ -76,6 +77,7 @@ async def wrap_api(*args, **kwargs) -> web.Response | web.StreamResponse: ) except HassioError as err: _LOGGER.exception("Unexpected error during API call: %s", err) + await async_capture_exception(err) return api_return_error(err) if isinstance(answer, (dict, list)): @@ -124,6 +126,7 @@ async def wrap_api(*args, **kwargs) -> web.Response | web.StreamResponse: ) except HassioError as err: _LOGGER.exception("Unexpected error during API call: %s", err) + await async_capture_exception(err) return api_return_error( err, error_type=error_type or const.CONTENT_TYPE_BINARY ) From a42b2b60b6440c437be302fff4689feabe257f8c Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Fri, 17 Apr 2026 15:21:30 +0200 Subject: [PATCH 3/3] Drop capture exception from set_boot_slot --- supervisor/os/manager.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/supervisor/os/manager.py b/supervisor/os/manager.py index b6b6099ca5b..57672fee2b8 100644 --- a/supervisor/os/manager.py +++ b/supervisor/os/manager.py @@ -22,7 +22,6 @@ ) from ..jobs.const import JobConcurrency, JobCondition from ..jobs.decorator import Job -from ..utils.sentry import async_capture_exception from .data_disk import DataDisk _LOGGER: logging.Logger = logging.getLogger(__name__) @@ -363,7 +362,6 @@ async def set_boot_slot(self, boot_name: str) -> None: RaucState.ACTIVE, self.get_slot_name(boot_name) ) except DBusError as err: - await async_capture_exception(err) raise HassOSSlotUpdateError( f"Can't mark {boot_name} as active!", _LOGGER.error ) from err