Review on #136313 (Greptile):
- The overlay offered "Change" to a custom-branch checkout and to runtimes
older than --set-channel, but Settings shows no selector for either, so
the link landed nowhere. The label stays; the action is passed only when
sourceUpdateChannel() allows a choice.
- An older checker omits aheadOfRelease yet may also pin an ahead checkout
to its own HEAD, so a missing field read as "on the release". source_check
now always sends the boolean, and the status line names the release only
on an explicit false.
The updates overlay (status-bar version click) showed no channel, so a
source install could not tell it was pinned to stable releases.
- VersionDetails (shared by About and the overlay) gains a Channel row for
source checkouts: "Stable releases", "Every commit (main)", or
"Branch: <name>". Packaged installs render no row; their channel is the
package's.
- The overlay passes a "Change" link that closes the dialog and deep-links
to Settings > About > Updates, where the selector and its confirm step
live. The channel is not switchable from the overlay itself.
- Current on stable, the status line names the release ("You're on the
latest stable release (v0.21.6)."). A forward-only checkout ahead of the
release keeps the generic line: source_check now reports
`aheadOfRelease`, since its pinned target is HEAD and would otherwise
look identical to being on the release.
Review on #136086 (Greptile):
- The failure no longer travels through a module dict. `_resolve_release`
raises; `_resolve_stable` describes the exception; `resolve_source_release`
stays the `(None, None)` wrapper for its other callers.
- A token GitHub rejects (401) retries anonymously; a 403 on that retry was
described as the user's GITHUB_TOKEN quota. `_request` now marks the
raised error with whether that request carried a token.
- The test server closes its listening socket and joins its thread.
Review findings on #135949 (Greptile, @unsupportedpastels):
- An older runtime (v0.21.6) answers checks but rejects --set-channel, so the
selector failed on every click. source_check now marks its reply
`channelSelectable`; Desktop shows the row only when the install says so.
- The confirmed choice painted only after the forced check returned. The row
now shows it at once, and a failure re-reads the install instead of guessing.
- A checkout following a custom branch rendered as "Every commit" (main). The
row now appears only for stable, or for main itself.
`hermes update` and the Desktop update button on an official source
checkout now move between published vX.Y.Z releases instead of tracking
main commit by commit, and `hermes update --set-channel stable` works.
- stable resolves through R2 when a record exists; until one is published
it falls back to the latest published GitHub release (non-draft,
non-prerelease, strict vX.Y.Z, origin tag verified). It used to fail
with "Channel object not found: releases/channels/stable.json".
- default_channel(): an official-origin source checkout defaults to
stable; forks, mirrors and checkout-less trees keep main. Package
installs (MSIX, Store, bundled, electron-updater) are untouched.
- An install that never chose a channel only moves forward: a checkout
already newer than the latest release (every install that tracked main)
stays put until a newer release ships. --set-channel stable is the
explicit consent to land exactly on the release commit.
- Same rule in --check, the apply path and the passive source_check the
Desktop and banner use.
An empty ls-remote advertisement cannot tell "merged and deleted upstream"
from "never pushed". The source check now re-pins a missing branch to main
only when the local branch was once published (remote-tracking ref or a
configured upstream) and git cherry finds no commits missing from main.
Otherwise it keeps the branch and reports branch-local-only.
Diagnosis from #105053 by @0xalydev and #105055 by @kokhlo, which targeted
the Electron healer since moved into hermes_cli/source_check.py.
Fixes#105042
check_for_updates had grown to ~CC 75 over 128 lines, mixing install gating,
checkout probing, cache freshness, channel resolution, remote-tip fetch and
result shaping. It now reads as orchestration over cohesive helpers:
_unsupported_reason, _read_checkout (_Checkout facts), _cached_status /
_write_cache, _resolve_channel, _check_branch (_branch_remote,
_heal_deleted_branch, _behind_count). Every function is below CC 30
(check_for_updates 26).
Behaviour, public signature and return shape are unchanged; module-level
seams tests patch (_git_stdout, _git_ok, _branch_tip, resolve_source_target,
time, ...) are still read from this module at call time.
Conflict resolutions and semantic fixups:
- utils.py / hermes_yaml.py: main widened ruamel's round-trip emitter so a long
double-quoted scalar is never folded after an escaped backslash. pm-clean builds
every rt emitter through hermes_yaml.roundtrip_yaml(), so the width lives there
(ROUNDTRIP_YAML_WIDTH moves with it); xai_retirement imports it from hermes_yaml.
- hermes_cli/banner.py: keep pm-clean's removal of the banner update check. Main's
GIT_NO_LAZY_FETCH fix for it applies to its replacement, source_check: every
read-only probe (source_git_env) now refuses promisor lazy fetches, and the
partial-clone test targets that probe (red without the flag).
- .github/workflows/tests.yml: keep setup-pm; main's uv pin bump does not apply.
Main's WAL-capable SQLite gates are kept, run against $HERMES_PYTHON (the
PM-pinned interpreter, SQLite 3.53.1). The e2e step takes main's
--include-integration invocation.
- apps/desktop: package.json has no build block here, so main's macOS locale-marker
restore joins the darwin branch of the existing after-pack.mjs, and its test
loads the hook from electron-builder.config.cjs and imports PlatformPackager
from app-builder-lib's root (electron-builder 27 exports no ./out paths). The
win32 row is dropped: this hook sanitizes and signs PE trees on win32 by design.
- reconciliation.ts: main's rowId hydration (#119326) was merged into the first of
pm-clean's split helpers only; the resolver is now one helper both halves use.
- en.ts: both sides' keys kept. tests/tools/test_lazy_deps.py stays deleted.
- Tests main added with `import yaml` use hermes_yaml, like the rest of the tree.
The passive source check moved into hermes_cli/source_check.py during the
pm/ rewrite, and with it the GitHub calls left the desktop — but the four
main fixes that lived on the desktop side (gh-CLI token fallback
c61a11474b, anonymous retry e79ba2d7f2, rate-limit headers
c7d4d32800, failure copy 66a13703b3) did not follow. `_request` sent no
Authorization, so every client behind one NAT/VPN exit shared the anonymous
60/hour budget, and `_branch_tip` collapsed every failure into "Could not
resolve the remote branch tip." — a 403 rate limit read as a Hermes bug.
hermes_cli/github_api.py owns the credential ladder (GITHUB_TOKEN, GH_TOKEN,
`gh auth token` cached per process, anonymous; a 401 on a token retries
anonymously) and the failure copy (rate limit with reset time and the
GITHUB_TOKEN remedy, 5xx as GitHub trouble, else the status/transport
error). `_branch_tip` returns that reason and check_for_updates puts it in
the message.
apps/desktop/electron/github-api-auth.ts had no caller left (the desktop no
longer talks to api.github.com) — removed with its test; the behaviour now
lives where the request is made.
build_deb.sh wrote HERMES_DESKTOP_VARIANT=bundled, so the Termux stamp
carried payload=bundled and every 'bundled' reader treated the tree as
the repo/ of an Electron payload. hermes uninstall --data then called
resolve_bundle_layout on it and rejected the plan: the deb has no
enclosing app to protect, so data-only removal was impossible on Termux.
Add a 'runtime' variant to write_install_stamp.py for a sealed CLI
runtime with no desktop app around it. It stays sealed for the update
gate and channel identity (source_check, update_channel accept it next
to bundled/light) while is_bundled_payload keeps answering False, so
cleanup planning protects the APT-owned package tree the ordinary way
and never asks where the app is.
Use the exact target root and profile for branch, release, and cache decisions. Keep the desktop as a transport and handoff adapter. Remove the competing TypeScript checker and switch the banner, dashboard, and updater count consumers.
Preserve fork origins, unknown counts, publication checks, old-probe recovery, and official SSH branch healing through public HTTPS. Keep the historical unstamped-root policy unchanged.