From 2434ee787e85c45eb370cad30dfd2a2b51af6198 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Tue, 27 Oct 2026 06:48:01 -0700 Subject: [PATCH] fix(update): a release-tag fetch failure is reported, and never crashes the update The tag's rev-parse sat outside the try, so an OSError (a missing git, or a custody refusal) escaped _cmd_update_impl, and a failed tag fetch was swallowed silently, leaving every version reader on the previous release, the symptom the fetch exists to prevent. Both are inside the best-effort block now, and a miss logs a warning and prints one line naming the tag. (itsflownium review on #144894, #3.) --- hermes_cli/update_cmd_check.py | 23 +++++++++++++++---- .../test_update_release_tag_fetch.py | 16 +++++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/hermes_cli/update_cmd_check.py b/hermes_cli/update_cmd_check.py index 8c10145d77..937c0049b8 100644 --- a/hermes_cli/update_cmd_check.py +++ b/hermes_cli/update_cmd_check.py @@ -7,11 +7,14 @@ per function, so test patches on ``hermes_cli.update_cmd`` and the origin module from __future__ import annotations +import logging import subprocess import sys from pathlib import Path from typing import Any +logger = logging.getLogger(__name__) + def _git(git_cmd: list[str], root: Path, args: list[str], **kwargs: Any) -> subprocess.CompletedProcess[str]: from hermes_cli._subprocess_compat import windows_hide_flags @@ -284,13 +287,23 @@ def fetch_release_tag(git_cmd: list[str], root: Path, request: dict) -> None: from hermes_cli.update_cmd import NETWORK_GIT_TIMEOUT_SECONDS tag = request.get("release_tag") - if not tag or _git(git_cmd, root, ["rev-parse", "--verify", "--quiet", f"refs/tags/{tag}"]).returncode == 0: + if not tag: return try: - _git(git_cmd, root, ["fetch", "--no-tags", "origin", f"refs/tags/{tag}:refs/tags/{tag}"], - timeout=NETWORK_GIT_TIMEOUT_SECONDS, **no_prompt_git_kwargs()) - except (OSError, subprocess.SubprocessError): - pass + if _git(git_cmd, root, ["rev-parse", "--verify", "--quiet", f"refs/tags/{tag}"]).returncode == 0: + return + fetched = _git(git_cmd, root, ["fetch", "--no-tags", "origin", f"refs/tags/{tag}:refs/tags/{tag}"], + timeout=NETWORK_GIT_TIMEOUT_SECONDS, **no_prompt_git_kwargs()) + detail = (fetched.stderr or "").strip().splitlines()[-1:] if fetched.returncode else None + except (OSError, subprocess.SubprocessError) as exc: # custody refusals are OSErrors too + detail = [str(exc)] + if detail is not None: + # Not fatal (a mirror may lack the tag), but say so: without it every version reader keeps + # naming the previous release, the symptom this fetch exists to prevent. + reason = detail[0] if detail else "git fetch failed" + logger.warning("Could not fetch release tag %s: %s", tag, reason) + print(f"⚠ Could not fetch the {tag} tag ({reason}); the version shown may name the previous " + "release until the next update fetches it.") def hold_channel_release_behind_runtime(git_cmd, target_ref: str, channel: str) -> str | None: diff --git a/tests/hermes_cli/test_update_release_tag_fetch.py b/tests/hermes_cli/test_update_release_tag_fetch.py index 4be95a82f1..2463616f23 100644 --- a/tests/hermes_cli/test_update_release_tag_fetch.py +++ b/tests/hermes_cli/test_update_release_tag_fetch.py @@ -6,8 +6,11 @@ the install stamp and the receipt still named the previous release. """ import subprocess +import sys from types import SimpleNamespace +import pytest + from hermes_cli import update_cmd_check from hermes_cli.source_releases import SourceTarget from hermes_cli.version_info import _git_version_info @@ -47,3 +50,16 @@ def test_stable_update_fetch_brings_the_release_tag(tmp_path, monkeypatch): info = _git_version_info(checkout) assert (info.commit, info.base_version, info.display_version) == (release, "0.22.3", "0.22.3") + + +@pytest.mark.parametrize("git_cmd", [["git"], [sys.executable + "-missing-git"]]) +def test_an_unfetchable_release_tag_is_reported_never_fatal(tmp_path, capsys, git_cmd): + """Best effort means the update goes on, but not silently: without the tag every version + reader keeps naming the previous release. A git that cannot even run (missing binary, a + custody refusal: both OSErrors) is the same non-fatal miss, not a crash of the update.""" + _git(tmp_path, "init", "-q", "-b", "main") # no origin: the fetch itself fails + + update_cmd_check.fetch_release_tag(git_cmd, tmp_path, {"release_tag": "v9.9.9"}) + + assert "Could not fetch the v9.9.9 tag" in capsys.readouterr().out +