fix(update): hourly check interval + wait for fresh fetch before pre-scan prompt

This commit is contained in:
Alex Schapiro
2026-07-28 05:09:58 +00:00
parent ac0014fe65
commit 9821ada7c5
3 changed files with 34 additions and 7 deletions
+2 -1
View File
@@ -15,6 +15,7 @@ from typing import TYPE_CHECKING, Any, ClassVar
if TYPE_CHECKING:
from pygments.token import _TokenType
from textual.timer import Timer
from rich.align import Align
@@ -352,7 +353,7 @@ class VulnerabilityDetailScreen(ModalScreen): # type: ignore[misc]
if not token_value:
continue
color = None
tt = token_type
tt: _TokenType | None = token_type
while tt:
if tt in colors:
color = colors[tt]
+11 -6
View File
@@ -1,7 +1,7 @@
"""Update notifications and self-update for the strix CLI.
Follows the pattern used by tools like gh, uv, and pip: a background,
rate-limited (once per 24h) check against the release source, a cached
rate-limited (once per hour) check against the release source, a cached
result in ``~/.strix``, a non-intrusive notice with the upgrade command
for the detected install method, and a ``strix --update`` self-update
path for the standalone binary install.
@@ -37,8 +37,9 @@ logger = logging.getLogger(__name__)
GITHUB_REPO = "usestrix/strix"
PYPI_PACKAGE = "strix-agent"
CHECK_INTERVAL_SECONDS = 24 * 60 * 60
CHECK_INTERVAL_SECONDS = 60 * 60
REQUEST_TIMEOUT_SECONDS = 5
PROMPT_JOIN_TIMEOUT_SECONDS = 3.0
_CACHE_PATH = Path.home() / ".strix" / "update-check.json"
@@ -175,7 +176,7 @@ def _refresh_cache() -> None:
def start_background_check() -> None:
"""Refresh the cached latest-version info in a daemon thread (at most once per 24h)."""
"""Refresh the cached latest-version info in a daemon thread (at most once per hour)."""
global _background_thread # noqa: PLW0603
if _is_disabled():
return
@@ -187,12 +188,16 @@ def start_background_check() -> None:
_background_thread.start()
def get_available_update(*, respect_skip: bool = True) -> str | None:
def get_available_update(
*,
respect_skip: bool = True,
join_timeout: float = 0.2,
) -> str | None:
"""Return the newer version from the cache, or None if up to date / unknown."""
if _is_disabled():
return None
if _background_thread is not None:
_background_thread.join(timeout=0.2)
_background_thread.join(timeout=join_timeout)
cache = _read_cache()
latest = cache.get("latest_version")
current = get_version()
@@ -239,7 +244,7 @@ def prompt_update_if_available(console: Console) -> bool:
Returns True if strix was updated (caller should re-exec / exit).
"""
latest = get_available_update()
latest = get_available_update(join_timeout=PROMPT_JOIN_TIMEOUT_SECONDS)
if not latest or not sys.stdin.isatty() or not sys.stdout.isatty():
return False
console.print()
+21
View File
@@ -132,6 +132,27 @@ def test_write_cache_preserves_existing_fields() -> None:
assert cache == {"latest_version": "1.2.3", "checked_at": 123.0, "skipped_version": "9.9.9"}
def test_prompt_join_waits_for_fresh_fetch(monkeypatch: pytest.MonkeyPatch) -> None:
update_check._CACHE_PATH.write_text(
json.dumps({"latest_version": "1.0.0", "checked_at": time.time() - 2 * 60 * 60})
)
monkeypatch.setattr(update_check, "get_version", lambda: "1.0.0")
def slow_fetch() -> str:
time.sleep(0.5)
return "9.9.9"
monkeypatch.setattr(update_check, "_fetch_latest_version", slow_fetch)
update_check.start_background_check()
assert update_check.get_available_update(join_timeout=0.0) is None
assert (
update_check.get_available_update(
join_timeout=update_check.PROMPT_JOIN_TIMEOUT_SECONDS,
)
== "9.9.9"
)
def test_get_upgrade_command_all_methods() -> None:
assert update_check.get_upgrade_command("binary") == "strix --update"
assert update_check.get_upgrade_command("pipx") == "pipx upgrade strix-agent"