service-update-alerts/update_monitor/checker.py
Jeena 7e15e63dae classifier: Point at gemma4:26b and retry transient fetches
Every classification returned "HTTP Error 404" because the configured
model gemma4:26b-64k no longer exists on the Ollama host: the tag was
retired upstream and the local weights were deleted, so all pending
updates fell back to manual review. Switch to the current gemma4:26b
tag (25.2B, 262k context) and make it explicit in .env so the
classifier no longer depends on the code default matching reality.

PieFed intermittently returned an empty body, which surfaced as a
current version error in the digest. Retry version fetches with a
short backoff so one bad response no longer triggers an alert.

Leave-one-out validation for the new model: immich 6/7 (the miss is
the conditional-breaking example where the model follows the written
rule that thresholds below the running version do not apply),
freshrss 6/6, uptime-kuma 4/4.
2026-09-14 05:37:37 +00:00

112 lines
4.4 KiB
Python

import json
import time
from typing import Any, Dict, List, Optional, Tuple
from .comparator import compare_versions
from .extractor import build_upstream_fallback, extract_version
from .fetcher import authenticate_service, build_headers, fetch_response, fetch_socket_info
from .models import ServiceConfig
def fetch_version_with_retries(
url: str,
timeout: float,
user_agent: str,
extra_headers: Optional[Dict[str, str]] = None,
attempts: int = 3,
) -> tuple[str, Dict[str, str]]:
"""Fetch a version endpoint with retries for transient failures
(empty bodies, timeouts)."""
last_exc: Optional[Exception] = None
for attempt in range(attempts):
try:
body, headers = fetch_response(url, timeout, user_agent, extra_headers)
if body.strip():
return body, headers
last_exc = ValueError("empty response body")
except Exception as exc:
last_exc = exc
if attempt < attempts - 1:
time.sleep(2 * (attempt + 1))
raise last_exc if last_exc else RuntimeError("fetch failed")
def check_service(service: ServiceConfig, timeout: float, user_agent: str) -> Dict[str, Any]:
result: Dict[str, Any] = {
"name": service.name,
"current": None,
"latest": None,
"current_error": None,
"latest_error": None,
"upstream_url": service.upstream_latest_version_url,
"release_notes": None,
}
if service.current_version_url and service.current_version_extract:
try:
headers = build_headers(service.current_version_headers)
if service.login:
login_cookie = authenticate_service(service, timeout, user_agent)
if login_cookie:
headers.setdefault("Cookie", login_cookie)
body, response_headers = fetch_version_with_retries(
service.current_version_url, timeout, user_agent, headers
)
result["current"] = extract_version(body, service.current_version_extract, response_headers)
except Exception as exc:
result["current_error"] = str(exc)
elif service.login_socket:
try:
result["current"] = fetch_socket_info(service, timeout)
except Exception as exc:
result["current_error"] = str(exc)
if service.upstream_latest_version_url and service.upstream_latest_extract:
headers = build_headers(service.upstream_latest_headers)
try:
body, response_headers = fetch_version_with_retries(
service.upstream_latest_version_url, timeout, user_agent, headers
)
result["latest"] = extract_version(body, service.upstream_latest_extract, response_headers)
if result["latest"] is None:
raise ValueError("Latest version extraction returned empty")
try:
parsed = json.loads(body)
if isinstance(parsed, dict):
result["release_notes"] = parsed.get("body") or None
except (json.JSONDecodeError, AttributeError):
pass
except Exception as exc:
fallback = build_upstream_fallback(service.upstream_latest_version_url)
if fallback:
try:
body, response_headers = fetch_response(fallback["url"], timeout, user_agent, headers)
result["latest"] = extract_version(body, fallback["extract"], response_headers)
except Exception as fallback_exc:
result["latest_error"] = str(fallback_exc)
else:
result["latest_error"] = str(exc)
return result
def build_summary(results: List[Dict[str, Any]]) -> Tuple[str, bool]:
updates = [r for r in results if compare_versions(r["current"], r["latest"]) == 1]
errors = [r for r in results if r["current_error"] or r["latest_error"]]
lines = []
if updates:
lines.append("Updates available:")
for r in updates:
lines.append(f"- {r['name']}: {r['current'] or 'unknown'} -> {r['latest'] or 'unknown'}")
if errors:
lines.append("Errors:")
for r in errors:
if r["current_error"]:
lines.append(f"- {r['name']}: current version error")
if r["latest_error"]:
lines.append(f"- {r['name']}: latest version error")
if not lines:
return "All services up to date.", False
return "\n".join(lines), True