fix(update): follow the main branch when its channel record is unpublished

A source checkout on the main channel failed every update check with
"Could not resolve the main source channel: Channel object not found:
releases/channels/main.json" until R2 publishes that record, in the CLI
and in Desktop (which asks hermes_cli.source_check). main IS the source
branch -- its record can only add a retirement -- so a missing main record
now resolves to the main branch and the update continues via git. Other
channels, and transient read failures for main, still refuse.
This commit is contained in:
ethernet
2026-09-24 14:16:17 -04:00
parent b9a5e2d022
commit 1dd9a9955d
2 changed files with 30 additions and 3 deletions

View File

@@ -64,7 +64,8 @@ def _resolve_channel(name: str, repository: str):
ChannelReader owns HTTPS, authority and digests. The source adapter below
admits its retirement constraints before any checkout operation. No legacy
GitHub fallback is allowed when a record is unavailable.
GitHub fallback is allowed when a record is unavailable; the one exception
is an unpublished ``main`` record, which resolves to the main branch.
"""
from hermes_cli.release_channels import ChannelReader
@@ -73,11 +74,18 @@ def _resolve_channel(name: str, repository: str):
def resolve_source_target(channel: str, git_cmd=None, cwd=None, *, repository=None) -> SourceTarget:
"""Resolve every subscription, including default labels, through R2."""
from hermes_cli.release_channels import validate_name
from hermes_cli.release_channels import ChannelNotFound, validate_name
validate_name(channel)
repository = repository or source_repository(git_cmd, cwd)
resolved = _resolve_channel(channel, repository)
try:
resolved = _resolve_channel(channel, repository)
except ChannelNotFound:
if channel != "main":
raise
# main IS the source branch; its record can only add a retirement.
# Until one is published, a checkout keeps following the branch via git.
return SourceTarget(channel, channel, repository, branch="main")
terminal = resolved.terminal
if terminal["repository"].lower() != repository.lower():
raise ValueError("Channel repository does not match this source installation")

View File

@@ -153,6 +153,25 @@ def test_missing_channel_cannot_fall_back_to_main(source, monkeypatch, channel):
assert git(source.root, "rev-parse", "HEAD") == before
def test_unpublished_main_record_keeps_following_the_git_branch(source, monkeypatch):
"""main IS the source branch: until R2 publishes its record, a checkout
still updates via git instead of failing on a missing channel object."""
from hermes_cli import source_check
from hermes_cli.release_channels import ChannelNotFound
set_install_channel("main", source.root)
def unpublished(name, repository):
raise ChannelNotFound(f"Channel object not found: releases/channels/{name}.json")
monkeypatch.setattr(source_releases, "_resolve_channel", unpublished)
target = source_releases.resolve_source_target("main", ["git"], source.root)
assert target.branch == "main" and target.commit is None
status = source_check.check_for_updates(install_root=source.root, home=source.home, force=True)
assert "error" not in status, status
assert status["targetSha"] == source.commits[2]
with pytest.raises(ChannelNotFound):
source_releases.resolve_source_target("stable", ["git"], source.root)
def test_passive_check_reports_retirement_without_adopting_it(source, monkeypatch):
from hermes_cli import source_check, banner