From 2184fecc2c5e9c94278b415a500f535bbbf9ac1e Mon Sep 17 00:00:00 2001 From: Isky Date: Wed, 22 Apr 2026 12:21:03 -0700 Subject: [PATCH] fix: distinguish "no release yet" from "could not reach github" in --doctor `browser-harness --doctor` currently prints "(could not reach github)" whenever `/repos/.../releases/latest` does not return a tag -- including the common case where the repo has no releases published yet, which returns an HTTP 404. The message implies a network problem when the API is reachable and simply has nothing to report. Split the two cases in `_latest_release_tag`: return a (tag, reachable) pair so callers can tell them apart. Doctor now prints "(no release published yet)" when reachable, "(could not reach github)" only on actual HTTP/network failure. 404 results are cached alongside successful ones so repeated doctor runs don't re-hit the API. Co-Authored-By: Claude Opus 4.7 (1M context) --- admin.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/admin.py b/admin.py index cf86c79f..558f7c13 100644 --- a/admin.py +++ b/admin.py @@ -341,19 +341,30 @@ def _cache_write(data): def _latest_release_tag(force=False): - """Return latest release tag from GitHub, or None. Cached for 24h to avoid hammering the API.""" + """Return (tag, reachable) for the latest release on GitHub. + + tag is the release tag or None when no release has been published yet, or + when the API was unreachable and no cached value exists. + reachable is True when the API responded (even if there is no release), + False when the HTTP request failed. Cached for 24h to avoid hammering the API.""" + import urllib.error cache = _cache_read() now = time.time() - if not force and cache.get("tag") and now - cache.get("fetched_at", 0) < VERSION_CACHE_TTL: - return cache["tag"] + if not force and cache.get("fetched_at") and now - cache["fetched_at"] < VERSION_CACHE_TTL: + return cache.get("tag") or None, True try: req = urllib.request.Request(GH_RELEASES, headers={"Accept": "application/vnd.github+json"}) tag = json.loads(urllib.request.urlopen(req, timeout=5).read()).get("tag_name") or "" + except urllib.error.HTTPError as e: + if e.code == 404: # repo has no releases yet -- reachable, just empty + _cache_write({**cache, "tag": "", "fetched_at": now}) + return None, True + return cache.get("tag") or None, False except Exception: - return cache.get("tag") # fall back to last known + return cache.get("tag") or None, False tag = tag.lstrip("v") _cache_write({**cache, "tag": tag, "fetched_at": now}) - return tag or None + return (tag or None), True def _version_tuple(v): @@ -371,9 +382,10 @@ def _version_tuple(v): def check_for_update(): - """(current, latest, newer_available). latest may be None if the API was unreachable and no cache exists.""" + """(current, latest, newer_available). latest may be None if the API was unreachable + and no cache exists, or if no release has been published yet.""" cur = _version() - latest = _latest_release_tag() + latest, _ = _latest_release_tag() newer = bool(cur and latest and _version_tuple(latest) > _version_tuple(cur)) return cur, latest, newer @@ -489,7 +501,7 @@ def run_doctor(): daemon = daemon_alive() profile_use = shutil.which("profile-use") is not None api_key = bool(os.environ.get("BROWSER_USE_API_KEY")) - latest = _latest_release_tag() + latest, reachable = _latest_release_tag() # Only claim an update when we know the installed version — `cur or "(unknown)"` # for display would otherwise be parsed as (0,) and flag every latest as newer. newer = bool(cur and latest and _version_tuple(latest) > _version_tuple(cur)) @@ -505,6 +517,8 @@ def row(label, ok, detail=""): print(f" version {cur_display} ({mode})") if latest: print(f" latest release {latest}" + (" (update available)" if newer else "")) + elif reachable: + print(" latest release (no release published yet)") else: print(" latest release (could not reach github)") row("chrome running", chrome, "" if chrome else "start chrome/edge and rerun `browser-harness --setup`")