From a61765ecc83cc6be39596d1985b0c6039e566c03 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Wed, 29 Jul 2026 15:24:22 +0200 Subject: [PATCH] Fix: Serena PyPI version check triggered by callback on main thread could delay agent startup Closes #1774 --- CHANGELOG.md | 3 +++ src/serena/dashboard.py | 27 +++++++++++++-------------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99029dbd..f89ae818 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,9 @@ Status of the `main` branch. Changes prior to the next official version change w calling `subprocess.run` directly (e.g. for installation processes), so all such subprocesses get `stdin=DEVNULL` and can no longer interfere with the stdio MCP connection #1748 +* Dashboard: + - Fix: Serena PyPI version check triggered by callback on main thread could delay agent startup #1774 + * Hooks: - Add `serena-hooks --client=grok`, including Grok-native PreToolUse allow/deny output. - PreToolUse remind hook: coerce non-string shell command values instead of failing, and recognize diff --git a/src/serena/dashboard.py b/src/serena/dashboard.py index 33c97b83..e1eafd4b 100644 --- a/src/serena/dashboard.py +++ b/src/serena/dashboard.py @@ -10,7 +10,6 @@ import urllib.error import urllib.request from collections.abc import Callable from dataclasses import dataclass -from functools import cached_property from html import escape from pathlib import Path from typing import TYPE_CHECKING, Any, Optional, Self @@ -216,38 +215,38 @@ class SerenaDashboardAPI: self._news_ready = threading.Event() self._setup_routes() self._read_news = ReadNews.load() + self._newer_serena_version: str | None = None # register callback for config changes self._current_config_overview: dict[str, Any] | None = None self._agent.register_config_changed_callback(self._on_agent_config_changed) - # fetch remote news in background on startup (non-blocking) + # start threads for background computations + # * fetch remote news in background on startup (non-blocking) threading.Thread(target=self._fetch_news, daemon=True).start() + # * determine if a newer Serena version is available + threading.Thread(target=self._determine_newer_serena_version, daemon=True).start() @property def memory_log_handler(self) -> MemoryLogHandler: return self._memory_log_handler - @cached_property - def _newer_serena_version(self) -> str | None: - """The version of a newer serena-agent package release on PyPI, if any. - - The result is computed only once (on first access) and cached thereafter. - - :return: the latest version available on PyPI if it is newer than the running version, else None + def _determine_newer_serena_version(self) -> str | None: + """ + Checks for availability of a newer Serena version on PyPI and stores it in self._newer_serena_version if found. """ try: # query PyPI for the latest released version - latest_serena_version = PyPIPackageInfo("serena-agent").get_latest_version(timeout_secs=3) + latest_serena_version = PyPIPackageInfo("serena-agent").get_latest_version(timeout_secs=5) # compare against the running version latest_version = Version(latest_serena_version) current_version = Version(self._agent.version) - if not current_version.is_at_least(*latest_version.components): - return latest_serena_version + log.error("Latest available Serena version on PyPI: %s, current version: %s", latest_version, current_version) + if not current_version.is_at_least(*latest_version.components) or True: + self._newer_serena_version = latest_serena_version except Exception as e: - log.warning("Failed to check for newer Serena version on PyPI: %s", e) - return None + log.info("Failed to check for newer Serena version on PyPI: %s", e) def _setup_routes(self) -> None: @self._app.route("/")