From 713354bf56bd4e063fa5fbdac37eb0a19569985d Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Fri, 27 Mar 2026 09:13:48 +0100 Subject: [PATCH] Handle certain OSError in get_latest_mtime during directory walk (#6632) Besides file not found also catch "Too many levels of symbolic links" which can happen when there are symbolic link loops in the add-on/apps repository. Also improve error handling in the repository update process to catch OSError when checking for local modifications and raise a specific error that can be handled appropriately. Fixes SUPERVISOR-1FJ0 Co-authored-by: Claude Opus 4.6 --- supervisor/store/repository.py | 18 ++++++++++++------ supervisor/utils/__init__.py | 10 ++++++---- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/supervisor/store/repository.py b/supervisor/store/repository.py index 96e6c6f44..f6725d505 100644 --- a/supervisor/store/repository.py +++ b/supervisor/store/repository.py @@ -219,11 +219,18 @@ class RepositoryLocal(RepositoryBuiltin): super().__init__(coresys, BuiltinRepository.LOCAL.value, local_path, slug) self._latest_mtime: float | None = None + async def _get_latest_mtime(self) -> tuple[float, Path]: + """Get latest modification time of repository.""" + try: + return await self.sys_run_in_executor(get_latest_mtime, self.local_path) + except OSError as err: + self.coresys.resolution.check_oserror(err) + _LOGGER.error("Can't check local repository for modifications: %s", err) + raise StoreRepositoryUnknownError(repo=self.slug) from err + async def load(self) -> None: """Load addon repository.""" - self._latest_mtime, _ = await self.sys_run_in_executor( - get_latest_mtime, self.local_path - ) + self._latest_mtime, _ = await self._get_latest_mtime() async def update(self) -> bool: """Update add-on repository. @@ -231,9 +238,8 @@ class RepositoryLocal(RepositoryBuiltin): Returns True if the repository was updated. """ # Check local modifications - latest_mtime, modified_path = await self.sys_run_in_executor( - get_latest_mtime, self.local_path - ) + latest_mtime, modified_path = await self._get_latest_mtime() + if self._latest_mtime != latest_mtime: _LOGGER.debug( "Local modifications detected in %s repository: %s", diff --git a/supervisor/utils/__init__.py b/supervisor/utils/__init__.py index da031eaf6..a3457f094 100644 --- a/supervisor/utils/__init__.py +++ b/supervisor/utils/__init__.py @@ -1,6 +1,7 @@ """Tools file for Supervisor.""" import asyncio +import errno from functools import lru_cache from ipaddress import IPv4Address import logging @@ -146,10 +147,11 @@ def get_latest_mtime(directory: Path) -> tuple[float, Path]: if mtime > latest_mtime: latest_mtime = mtime latest_path = path - except FileNotFoundError: - # File might disappear between listing and stat. Parent - # directory modification date will flag such a change. - continue + except OSError as err: + if err.errno in (errno.ENOENT, errno.ELOOP): + _LOGGER.debug("Could not stat %s, skipping", path) + continue + raise return latest_mtime, latest_path