diff --git a/hermes_cli/update_cmd_fleet.py b/hermes_cli/update_cmd_fleet.py index c6d93cdfbc..a8491a0b2d 100644 --- a/hermes_cli/update_cmd_fleet.py +++ b/hermes_cli/update_cmd_fleet.py @@ -68,7 +68,6 @@ def _current_checkout_sha() -> str | None: from hermes_cli.update_cmd import _capture_head_sha, _m try: from hermes_cli.build_info import get_code_identity - sha = (get_code_identity(refresh=True) or {}).get("sha") return str(sha) if sha else None except Exception: @@ -96,7 +95,6 @@ def _receipt_reports_stale_runtime(expected_sha: str | None = None) -> bool: from hermes_cli.update_cmd import _current_checkout_sha try: from hermes_cli.update_receipt import read_latest_receipt - receipt = read_latest_receipt() except Exception: receipt = None @@ -139,11 +137,7 @@ def _pending_fleet_restart_needed() -> bool: def _warn_pending_fleet_restart(*, startup: bool = False) -> None: """Print the specific interrupted-update fleet-restart warning.""" stream = sys.stderr if startup else sys.stdout - print( - "⚠ A previous `hermes update` pulled new code but did not " - "restart running gateways.", - file=stream, - ) + print("⚠ A previous `hermes update` pulled new code but did not restart running gateways.", file=stream) print(" Gateways may still be serving pre-update modules (mixed sys.modules).", file=stream) if startup: print(" Run `hermes update` or `hermes gateway restart`.", file=stream) @@ -246,7 +240,6 @@ def _run_pending_fleet_restart() -> bool: if is_windows(): try: from hermes_cli import gateway_windows - if gateway_windows.is_installed(): gateway_windows.restart() except Exception as exc: @@ -295,12 +288,7 @@ def _apply_pending_fleet_restart_catchup() -> None: def _systemctl(cmd: list, *, timeout: float): """Run a systemctl (or sudo systemctl) invocation, capturing utf-8 text with a timeout.""" - return subprocess.run( - cmd, - capture_output=True, - text=True, encoding="utf-8", errors="replace", - timeout=timeout, - ) + return subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=timeout) def _systemctl_reset_and_restart(manage_cmd: list, svc_name: str): @@ -355,7 +343,6 @@ def _service_unit_supports_graceful_sigusr1_restart(svc_name: str) -> bool: def _warn_incomplete_gateway_fleet_restart(failed_units: list) -> None: """Print an explicit incomplete-update warning for unrestarted units.""" from hermes_cli.gateway import is_macos - if not failed_units: return ordered = list(dict.fromkeys(failed_units)) # de-dup, discovery order @@ -398,7 +385,6 @@ def _restart_launchd_gateway_after_update(*, supervision_verify: bool = True) -> launchd_restart, wait_for_launchd_gateway_supervision, ) - current_label = get_launchd_label() try: if not get_launchd_plist_path().exists(): @@ -439,11 +425,7 @@ def _restart_launchd_gateway_after_update(*, supervision_verify: bool = True) -> return [], [current_label] -def _restart_macos_launchd_gateways( - restarted_services: list, - failed_or_stale_units: list, - drain_budget: float, -) -> None: +def _restart_macos_launchd_gateways(restarted_services: list, failed_or_stale_units: list, drain_budget: float) -> None: """Restart every launchd-managed gateway after an update (macOS). The pull is shared across profiles, so every ``ai.hermes.gateway*`` LaunchAgent @@ -460,7 +442,6 @@ def _restart_macos_launchd_gateways( _locate_launchd_gateway_service, _wait_for_launchd_service_pid, ) - _restarted, _failed = _restart_launchd_gateway_after_update(supervision_verify=True) restarted_services.extend(_restarted) failed_or_stale_units.extend(_failed) @@ -479,9 +460,7 @@ def _restart_macos_launchd_gateways( if old_pid is not None and old_pid > 0: print(f" → {label}: draining (up to {int(drain_budget)}s)...") graceful_ok = _graceful_restart_via_sigusr1(old_pid, drain_timeout=drain_budget) - if graceful_ok and _wait_for_launchd_service_pid( - label, old_pid=old_pid, timeout=10.0, domain=domain - ): + if graceful_ok and _wait_for_launchd_service_pid(label, old_pid=old_pid, timeout=10.0, domain=domain): # KeepAlive already respawned it on new code — a kickstart would kill it. restarted_services.append(label) continue @@ -517,7 +496,6 @@ def _surviving_gateway_pids_after_failed_restart(): """ try: from hermes_cli.gateway import find_gateway_pids - return list(find_gateway_pids(all_profiles=True)) except Exception as exc: # pragma: no cover - defensive logger.debug("Could not probe for surviving gateways after update: %s", exc) @@ -552,9 +530,7 @@ _SERVE_SKIP_REASON = ( ) -def _gateway_recovery_partition( - plan, *, skip_profiles: set[str] | None = None -) -> tuple[dict[str, str], list[dict]]: +def _gateway_recovery_partition(plan, *, skip_profiles: set[str] | None = None) -> tuple[dict[str, str], list[dict]]: """Partition pre-update runtimes into fresh-restart candidates and skips. Uses only the pre-checkout inventory: re-importing ``hermes_cli.gateway`` in the @@ -586,9 +562,7 @@ def _gateway_recovery_partition( reason = _DESKTOP_SERVE_SKIP_REASON if supervisor == "desktop" else _SERVE_SKIP_REASON else: continue - skipped.append( - {"profile": profile, "kind": str(kind), "supervisor": str(supervisor), "reason": reason} - ) + skipped.append({"profile": profile, "kind": str(kind), "supervisor": str(supervisor), "reason": reason}) return candidates, skipped @@ -631,7 +605,6 @@ def _drain_or_signal_gateway_for_update(pid: int, drain_budget: float, label: st _request_gateway_self_restart, probe_gateway_loop_liveness, ) - if _is_pid_ancestor_of_current_process(pid): print( f" → {label}: update is running inside this gateway's " @@ -640,10 +613,7 @@ def _drain_or_signal_gateway_for_update(pid: int, drain_budget: float, label: st ) return _request_gateway_self_restart(pid) if probe_gateway_loop_liveness(pid) == GATEWAY_LOOP_WEDGED: - print( - f" ⚠ {label}: gateway event loop is unresponsive — " - "skipping drain, forcing a bounded stop..." - ) + print(f" ⚠ {label}: gateway event loop is unresponsive — skipping drain, forcing a bounded stop...") _escalate_wedged_gateway(pid) return True print(f" → {label}: draining (up to {int(drain_budget)}s)...") @@ -785,16 +755,13 @@ def _restart_one_systemd_gateway_unit( ) -def _restart_systemd_gateway_units( - restarted_services, failed_or_stale_units, restarted_scoped_units, drain_budget -): +def _restart_systemd_gateway_units(restarted_services, failed_or_stale_units, restarted_scoped_units, drain_budget): """Restart every active hermes-gateway*/hermes-serve* systemd unit (user + system). Settled units → ``restarted_services`` (bare) and ``restarted_scoped_units`` (``scope/name``); failures → ``failed_or_stale_units``. Per-unit timeouts isolated. """ from hermes_cli.gateway import supports_systemd_services, _ensure_user_systemd_env - if not supports_systemd_services(): return _manage_cmd_cache: dict = {} @@ -859,7 +826,6 @@ class _GatewayRestartOutcome: """Best-effort ``record_gateway_restart`` from the current bookkeeping.""" with suppress(Exception): from hermes_cli.update_receipt import record_gateway_restart - record_gateway_restart( restarted_services=self.restarted_services, relaunched_profiles=self.relaunched_profiles, @@ -884,7 +850,6 @@ def _restart_manual_gateways(out: _GatewayRestartOutcome, _drain_budget) -> None _get_service_pids, _wait_for_gateway_exit, ) - # Exclude just-restarted service PIDs so we don't kill what systemd/launchd spawned. service_pids = _get_service_pids(all_profiles=True) manual_pids = find_gateway_pids(exclude_pids=service_pids, all_profiles=True) @@ -939,9 +904,7 @@ def _restart_manual_gateways(out: _GatewayRestartOutcome, _drain_budget) -> None if out.externally_supervised_profiles: names = ", ".join(out.externally_supervised_profiles) print(f" ✓ Handed gateway profile(s) back to their external supervisor: {names}") - unmapped_count = ( - len(out.killed_pids) - len(out.relaunched_profiles) - len(out.externally_supervised_profiles) - ) + unmapped_count = (len(out.killed_pids) - len(out.relaunched_profiles) - len(out.externally_supervised_profiles)) if unmapped_count: print(f" → Stopped {unmapped_count} manual gateway process(es)") print(" Restart manually: hermes gateway run") @@ -955,7 +918,6 @@ def _force_kill_stuck_gateways(killed_pids) -> None: moment, then SIGKILL remaining pre-update PIDs.""" with _best_effort('Post-restart survivor sweep failed: %s'): from hermes_cli.gateway import find_gateway_pids, _get_service_pids - _time.sleep(3.0) _surviving = find_gateway_pids(exclude_pids=_get_service_pids(all_profiles=True), all_profiles=True) # Only PIDs we already tried to kill; newer ones are left alone. @@ -964,7 +926,6 @@ def _force_kill_stuck_gateways(killed_pids) -> None: print() print(f" ⚠ {len(_stuck)} gateway process(es) ignored SIGTERM — force-killing") from gateway.status import get_process_start_time, terminate_pid - for pid in _stuck: with suppress(ProcessLookupError, PermissionError, OSError): # taskkill /T /F on Windows (no SIGKILL there), SIGKILL on POSIX. @@ -984,7 +945,6 @@ def _recover_after_restart_phase_abort( _warn_stale_serve_runtimes, _write_gateway_update_exit_code, ) - logger.debug("Gateway restart during update failed: %s", e) out.phase_errors.append(str(e)) # Restart output never printed: assume stale unless provably no gateway runs. @@ -1052,7 +1012,6 @@ def _restart_gateway_fleet_after_update(_pre_update_plan, gateway_mode: bool): every planned gateway is verifiably covered. """ from hermes_cli.update_cmd import _m, _write_gateway_update_exit_code - # All bookkeeping is declared before the try so abort recovery and fleet reconciliation # can read it even if the phase raises early. ``pre_restart_gateway_pids`` stays empty # until we are about to stop/drain, so an early exception has nothing to fail closed on, @@ -1087,13 +1046,11 @@ def _restart_gateway_fleet_after_update(_pre_update_plan, gateway_mode: bool): _get_service_pids, _wait_for_gateway_exit, ) - # Drain budget covers ``restart_after_turn_timeout`` and stop()'s # ``restart_drain_timeout`` so a gateway waiting on a turn isn't hard-killed; # units without SIGUSR1 wiring just time out into ``systemctl restart``. try: from hermes_cli.gateway import _get_restart_exit_wait_budget - _drain_budget = max(float(_get_restart_exit_wait_budget()), 45.0) except Exception: _drain_budget = 45.0 @@ -1112,9 +1069,7 @@ def _restart_gateway_fleet_after_update(_pre_update_plan, gateway_mode: bool): # macOS: EVERY ai.hermes.gateway* LaunchAgent (systemd parity). if is_macos(): with suppress(FileNotFoundError, ImportError): - _restart_macos_launchd_gateways( - out.restarted_services, out.failed_or_stale_units, _drain_budget - ) + _restart_macos_launchd_gateways(out.restarted_services, out.failed_or_stale_units, _drain_budget) _restart_manual_gateways(out, _drain_budget) @@ -1137,12 +1092,7 @@ def _restart_gateway_fleet_after_update(_pre_update_plan, gateway_mode: bool): def _print_legacy_units_warning() -> None: """Legacy hermes.service fights hermes-gateway.service over the bot token; warn on every update until migrated.""" - from hermes_cli.gateway import ( - has_legacy_hermes_units, - _find_legacy_hermes_units, - supports_systemd_services, - ) - + from hermes_cli.gateway import (has_legacy_hermes_units, _find_legacy_hermes_units, supports_systemd_services) if not (supports_systemd_services() and has_legacy_hermes_units()): return print() @@ -1170,7 +1120,6 @@ def _collect_fleet_snapshot(restart, rows_expected: bool) -> list: instead of no row at all. """ from hermes_cli.update_receipt import collect_fleet_versions - if not rows_expected: return collect_fleet_versions(pre_restart_pids=restart.pre_restart_gateway_pids) _fleet_deadline = _time.monotonic() + 30.0 @@ -1183,14 +1132,7 @@ def _collect_fleet_snapshot(restart, rows_expected: bool) -> list: return snapshot -def _verify_fleet_after_update( - restart, - *, - _pre_update_plan, - _windows_gateway_resume, - node_failures, - update_complete, -): +def _verify_fleet_after_update(restart, *, _pre_update_plan, _windows_gateway_resume, node_failures, update_complete): """Post-restart verification: legacy-unit warning, dashboard cleanup, stale serve probe, fleet version matrix, plan-vs-execution reconciliation, receipt finalize. @@ -1230,7 +1172,6 @@ def _verify_fleet_after_update( _fleet_snapshot: list = [] with _best_effort('Fleet version verification failed: %s'): from hermes_cli.update_receipt import print_fleet_version_matrix - # Cross-platform "rows expected" signal: (restarted_services or killed_pids) # never fires on Windows (pause/resume populates neither), so a healthy # resumed gateway yielded zero rows and exit 0. @@ -1257,11 +1198,7 @@ def _verify_fleet_after_update( # unaccounted one is a silent miss and escalates like a STALE/DOWN row. with _best_effort('Runtime-outcome reconciliation failed: %s'): if _pre_update_plan is not None and _pre_update_plan.runtimes: - from hermes_cli.update_inventory import ( - match_runtime_outcomes, - report_unaccounted_runtimes, - ) - + from hermes_cli.update_inventory import (match_runtime_outcomes, report_unaccounted_runtimes) _runtime_outcomes = match_runtime_outcomes( _pre_update_plan, restarted_services=restart.restarted_services, @@ -1280,13 +1217,11 @@ def _verify_fleet_after_update( restart.incomplete = True with suppress(Exception): import hermes_cli.update_receipt as _ur - if _ur._current is not None: _ur._current.data["runtime_outcomes"] = _runtime_outcomes with _best_effort('Update receipt finalize failed: %s'): from hermes_cli.update_receipt import finalize_update_receipt - _receipt_path = finalize_update_receipt( "partial" if restart.incomplete or not update_complete else "success", fleet=_fleet_snapshot, diff --git a/hermes_cli/update_cmd_maint.py b/hermes_cli/update_cmd_maint.py index e7bc050853..612296427f 100644 --- a/hermes_cli/update_cmd_maint.py +++ b/hermes_cli/update_cmd_maint.py @@ -56,7 +56,6 @@ _SQLITE_WAL_BUG_DETAIL = "SQLite {} still has the WAL-reset corruption bug" def _load_updates_cfg() -> dict: """``updates`` section of config.yaml; ``{}`` on any failure.""" from hermes_cli.config import load_config - cfg = load_config() or {} updates = cfg.get("updates", {}) if isinstance(cfg, dict) else {} return updates if isinstance(updates, dict) else {} @@ -116,7 +115,6 @@ def _print_curator_first_run_notice() -> None: disable it first. Silent on steady state.""" try: from agent import curator - if not curator.is_enabled(): return state = curator.load_state() @@ -148,10 +146,7 @@ def _print_fts_optimize_available_notice() -> None: """ try: from hermes_cli.config import load_config - - mode = str( - ((load_config() or {}).get("sessions") or {}).get("fts_optimize_notice", "advise") - ).strip().lower() + mode = str(((load_config() or {}).get("sessions") or {}).get("fts_optimize_notice", "advise")).strip().lower() except Exception: mode = "advise" if mode == "off": @@ -246,7 +241,6 @@ def _print_curator_recent_run_notice() -> None: ``last_run_summary_shown_at``. Silent when never run, already shown, or no rename info.""" try: from agent import curator - state = curator.load_state() except Exception: return @@ -352,7 +346,6 @@ def _read_project_version() -> str | None: from hermes_cli.update_cmd import _m try: import tomllib - with open(_m().PROJECT_ROOT / "pyproject.toml", "rb") as fh: # windows-footgun: ok — binary mode, tomllib requires bytes version = tomllib.load(fh).get("project", {}).get("version") return str(version) if version else None @@ -376,13 +369,8 @@ def _post_update_sqlite_runtime_status(): from hermes_cli.update_cmd import _m from hermes_constants import project_venv_dir from hermes_cli.sqlite_runtime import probe_sqlite_runtime - venv_dir = project_venv_dir(_m().PROJECT_ROOT) - python = ( - venv_python_path(venv_dir, windows=_m()._is_windows()) - if venv_dir is not None - else Path(sys.executable) - ) + python = (venv_python_path(venv_dir, windows=_m()._is_windows()) if venv_dir is not None else Path(sys.executable)) info = probe_sqlite_runtime(python) return info is not None and not info.wal_reset_vulnerable, info @@ -403,10 +391,7 @@ def _print_verified_update_completion(message: str) -> bool: return True print() print(f"⚠ Update partially complete — {_SQLITE_WAL_BUG_DETAIL.format(sqlite_info.sqlite_version_string)}.") - print( - " Rebuild the Hermes venv with a uv-managed Python, restart Hermes, " - "then verify with `hermes doctor`." - ) + print(" Rebuild the Hermes venv with a uv-managed Python, restart Hermes, then verify with `hermes doctor`.") return False @@ -423,12 +408,7 @@ def _clear_stale_sqlite_sidecars(db_path: Path) -> None: db_path.with_name(db_path.name + suffix).unlink(missing_ok=True) -def _print_update_summary( - *, - node_failures: list, - desktop_build_ok: bool, - pre_update_version: str | None, -) -> bool: +def _print_update_summary(*, node_failures: list, desktop_build_ok: bool, pre_update_version: str | None) -> bool: """Final banner. A failed Desktop rebuild is non-fatal but must not print ``✓ Update complete!``.""" from hermes_cli.update_cmd import _post_update_sqlite_runtime_status, _update_complete_message sqlite_runtime_ok, sqlite_info = _post_update_sqlite_runtime_status() @@ -473,7 +453,6 @@ def _restore_state_db_from_snapshot(state_path: Path, snap_state: Path) -> bool: """ from hermes_cli.backup import _foreign_db_holder_pids, verify_sqlite_integrity from hermes_cli.sqlite_safe_read import LiveConnectionError, offline_file_access - holders = _foreign_db_holder_pids(state_path) if holders: print( @@ -504,7 +483,6 @@ def _verify_and_restore_one_state_db(home: Path, *, label: str) -> None: Never raises: a guard that crashes the update tail is worse than what it detects.""" try: from hermes_cli.backup import _quick_snapshot_root, verify_sqlite_integrity - state_path = home / "state.db" if not state_path.exists(): return @@ -545,7 +523,6 @@ def _verify_and_restore_state_dbs_post_update() -> None: _verify_and_restore_one_state_db(home, label="default home") with _best_effort('Sibling-profile state.db guard sweep failed: %s'): from hermes_cli.backup import _sibling_profile_homes - for name, profile_home in _sibling_profile_homes(home): _verify_and_restore_one_state_db(profile_home, label=f"profile {name}") @@ -553,7 +530,6 @@ def _verify_and_restore_state_dbs_post_update() -> None: def _print_bundled_skills_sync_report() -> None: """Run ``sync_skills`` (copies new, updates changed, respects user deletions) and print its summary.""" from tools.skills_sync import sync_skills - result = sync_skills(quiet=True) if result["copied"]: print(f" + {len(result['copied'])} new: {', '.join(result['copied'])}") @@ -565,10 +541,7 @@ def _print_bundled_skills_sync_report() -> None: if result.get("cleaned"): print(f" − {len(result['cleaned'])} removed from manifest") if result.get("relocated"): - print( - f" → {len(result['relocated'])} moved to new upstream paths: " - f"{', '.join(result['relocated'])}" - ) + print(f" → {len(result['relocated'])} moved to new upstream paths: {', '.join(result['relocated'])}") if not result["copied"] and not result.get("updated"): print(" ✓ Skills are up to date") @@ -716,7 +689,6 @@ def _verify_state_db_after_snapshot(snapshot_id: str) -> None: gateway, Windows filter driver) can corrupt it and we'd otherwise exit 0 silently.""" from hermes_cli.backup import _quick_snapshot_root, verify_sqlite_integrity from hermes_cli.config import get_hermes_home - _src_path = get_hermes_home() / "state.db" if not _src_path.exists(): return @@ -736,10 +708,7 @@ def _verify_state_db_after_snapshot(snapshot_id: str) -> None: print(" ✓ Snapshot copy is valid — continuing update.") print(" If state.db is lost after update it will be auto-restored.") else: - print( - " ✗ Snapshot copy ALSO failed integrity — " - "the source was already corrupted before the backup." - ) + print(" ✗ Snapshot copy ALSO failed integrity — the source was already corrupted before the backup.") print() @@ -747,7 +716,6 @@ def _run_quick_snapshots() -> Optional[str]: """Quick snapshot of the root home plus every sibling profile; returns the root snapshot id.""" from hermes_cli.update_cmd import _record_update_step from hermes_cli.backup import create_quick_snapshot - snapshot_id = create_quick_snapshot( label="pre-update", keep=_PRE_UPDATE_SNAPSHOT_KEEP, @@ -761,7 +729,6 @@ def _run_quick_snapshots() -> Optional[str]: # under its own state-snapshots/. Best-effort per profile. with _best_effort('Sibling profile snapshots failed: %s'): from hermes_cli.backup import create_pre_update_snapshots_all_profiles - _sibling_snaps = create_pre_update_snapshots_all_profiles( keep=_PRE_UPDATE_SNAPSHOT_KEEP, max_file_size=_PRE_UPDATE_SNAPSHOT_MAX_FILE_SIZE, @@ -774,7 +741,6 @@ def _run_quick_snapshots() -> Optional[str]: ", ".join(f"{k}={v}" for k, v in sorted(_sibling_snaps.items())), ) import hermes_cli.update_cmd_config as _cfg - # The reader lives in update_cmd_config; write ITS module global, not ours. _cfg._LAST_SIBLING_SNAPSHOTS = _sibling_snaps return snapshot_id @@ -816,11 +782,9 @@ def _run_full_backup() -> None: size_bytes = 0 from hermes_cli.sizefmt import format_bytes - # display_hermes_home so the user sees ~/.hermes/... try: from hermes_constants import get_hermes_home, display_hermes_home - display_path = f"{display_hermes_home()}/{out_path.relative_to(get_hermes_home())}" except Exception: display_path = str(out_path) @@ -890,7 +854,6 @@ def _sync_profiles_after_update() -> None: # HERMES_HOME, so sync_skills()'s module-level HERMES_HOME cache can't skew it. with suppress(Exception): from hermes_cli.profiles import list_profiles, seed_profile_skills - all_profiles = list_profiles() if all_profiles: print() @@ -905,18 +868,13 @@ def _sync_profiles_after_update() -> None: # keep the credentials they were effectively using. with suppress(Exception): from hermes_cli.profiles import backfill_profile_envs - backfilled = backfill_profile_envs(quiet=True) if backfilled: print() - print( - f"→ Seeded .env for {len(backfilled)} profile(s) " - f"(copied from default): {', '.join(backfilled)}" - ) + print(f"→ Seeded .env for {len(backfilled)} profile(s) (copied from default): {', '.join(backfilled)}") with suppress(Exception): from plugins.memory.honcho.cli import sync_honcho_profiles_quiet - synced = sync_honcho_profiles_quiet() if synced: print(f"\n-> Honcho: synced {synced} profile(s)") @@ -933,7 +891,6 @@ def _refresh_cua_driver_after_update() -> None: refresh_cua_driver and sys.platform in ("darwin", "win32", "linux") and shutil.which("cua-driver") ): from hermes_cli.tools_config import install_cua_driver - print() print("→ Refreshing cua-driver (Computer Use)...") # require_confirmed_update: install only when check-update positively reports a @@ -951,7 +908,6 @@ def _print_post_update_notices_and_self_heals() -> None: # Windows launchers into the managed bin dir: in-checkout launchers were swept by the # autostash (--include-untracked) and updates never run install.ps1. No-op on POSIX. from hermes_cli._install_repair import migrate_windows_bin_path - migrate_windows_bin_path(_m().PROJECT_ROOT) for message, step in ( @@ -982,7 +938,6 @@ def _run_post_update_maintenance( the update summary (verdict returned), and best-effort notices/self-heals. Every step is isolated so none can fail the update.""" from hermes_cli.update_cmd import _check_and_apply_config_migration, _m - # macOS TCC: Desktop bundles are re-signed each update, so old grants can go stale # (toggle ON, yet macOS re-prompts with no Allow button). Tell users how to re-grant. if sys.platform == "darwin" and had_desktop_app_before_update: @@ -998,7 +953,6 @@ def _run_post_update_maintenance( # macOS TCC interpreter anchor; boot-gated — a failed probe leaves the venv untouched. try: from hermes_cli.macos_tcc_anchor import ensure_tcc_anchor - ensure_tcc_anchor() except Exception: logger.debug("macOS TCC anchor refresh skipped", exc_info=True) @@ -1010,7 +964,6 @@ def _run_post_update_maintenance( # Seed the model-catalog cache from the checkout instead of a bot-gated, flaky fetch. with _best_effort('Model catalog seed during update failed: %s'): from hermes_cli.model_catalog import seed_cache_from_checkout - if seed_cache_from_checkout(_m().PROJECT_ROOT): print(" ✓ Model catalog cache refreshed from checkout") diff --git a/hermes_cli/update_cmd_stash.py b/hermes_cli/update_cmd_stash.py index ca20deb0d9..e02e3a4329 100644 --- a/hermes_cli/update_cmd_stash.py +++ b/hermes_cli/update_cmd_stash.py @@ -61,14 +61,8 @@ def _stash_local_changes_if_needed(git_cmd: list[str], cwd: Path) -> Optional[st # Non-zero but entry created: push saved everything yet couldn't delete some # untracked files (e.g. root-owned dir). Not a failure — continue. _print_nonempty(push.stderr) - print( - " ⚠ Some untracked files could not be removed from the " - "working tree (permission denied)." - ) - print( - " They were still saved to the stash and were left in " - "place — the update will continue." - ) + print(" ⚠ Some untracked files could not be removed from the working tree (permission denied).") + print(" They were still saved to the stash and were left in place — the update will continue.") # A partially-failed push also skips cleanup of TRACKED modifications; # they'd break the following pull. Safe to reset: all is in the stash. subprocess.run(git_cmd + ["reset", "--hard", "HEAD"], cwd=cwd, capture_output=True) @@ -78,9 +72,7 @@ def _stash_local_changes_if_needed(git_cmd: list[str], cwd: Path) -> Optional[st if push.stderr.strip(): print(f" {push.stderr.strip().splitlines()[0]}") print(" Commit, stash, or clean up your local changes manually, then re-run `hermes update`.") - raise subprocess.CalledProcessError( - push.returncode, push.args, output=push.stdout, stderr=push.stderr - ) + raise subprocess.CalledProcessError(push.returncode, push.args, output=push.stdout, stderr=push.stderr) return stash_ref @@ -102,7 +94,6 @@ def _warn_orphaned_update_autostashes(git_cmd: list[str], cwd: Path) -> int: Deliberately NOT a GC: a stash may be the only copy of the user's work, so Hermes never drops one. """ from hermes_cli.update_cmd import _git_run - try: stash_list = _git_run(git_cmd, ["stash", "list", "--format=%gd %s"], cwd) if stash_list.returncode != 0: @@ -227,9 +218,7 @@ def _reject_unsafe_stash_restore( print(f" {line}") current_untracked = _git_untracked_paths(git_cmd, cwd) - restored_untracked = ( - current_untracked - preexisting_untracked if current_untracked is not None else set() - ) + restored_untracked = (current_untracked - preexisting_untracked if current_untracked is not None else set()) reset = _git_quiet(git_cmd, ["reset", "--hard", "HEAD"], cwd) clean = None if restored_untracked: @@ -336,10 +325,7 @@ def _restore_stashed_changes( restored_python = _restored_python_paths(git_cmd, cwd) if restored_python is None: - reject( - "restored Python source discovery", - "could not determine which restored Python files require validation", - ) + reject("restored Python source discovery", "could not determine which restored Python files require validation") syntax_ok, failing_path, syntax_error = _validate_python_files_syntax(cwd, restored_python) if not syntax_ok: reject(failing_path or "restored Python source", syntax_error) diff --git a/hermes_cli/update_cmd_zip.py b/hermes_cli/update_cmd_zip.py index 001d41b6f8..c9ec38e451 100644 --- a/hermes_cli/update_cmd_zip.py +++ b/hermes_cli/update_cmd_zip.py @@ -192,7 +192,6 @@ def _extract_zip_safely(zip_path: str, tmp_dir: str) -> None: symlinks, and a compromised mirror could use them to plant files anywhere.""" import stat as _stat import zipfile - with zipfile.ZipFile(zip_path, "r") as zf: tmp_dir_real = os.path.realpath(tmp_dir) for member in zf.infolist(): @@ -213,7 +212,6 @@ def _download_and_swap_zip(branch: str, zip_url: str) -> None: import tempfile from urllib.request import urlretrieve - print("→ Downloading latest version...") tmp_dir = tempfile.mkdtemp(prefix="hermes-update-") try: @@ -324,7 +322,6 @@ def _reinstall_python_deps_after_zip(active_tool_dependencies) -> None: ) from hermes_cli.managed_uv import ensure_uv, update_managed_uv - # Keep managed uv current — runs `uv self update` if we already have one. update_managed_uv() @@ -337,7 +334,6 @@ def _reinstall_python_deps_after_zip(active_tool_dependencies) -> None: # Same UV-env isolation as the main update path: a user-level UV_PYTHON_INSTALL_DIR / UV_PYTHON # from unrelated software must not steer which interpreter uv resolves here. from hermes_cli.managed_uv import managed_python_env - uv_env = managed_python_env() uv_env["VIRTUAL_ENV"] = str(_m().PROJECT_ROOT / "venv") if _m()._is_termux_env(uv_env): @@ -439,7 +435,6 @@ def _update_via_zip(args, *, had_desktop_app_before_update: bool = False) -> boo # Seed the model-catalog disk cache from the fresh checkout (same rationale as _cmd_update_impl). Non-fatal. with _best_effort('Model catalog seed during zip update failed: %s'): from hermes_cli.model_catalog import seed_cache_from_checkout - if seed_cache_from_checkout(_m().PROJECT_ROOT): print(" ✓ Model catalog cache refreshed from checkout") @@ -460,6 +455,5 @@ def _update_via_zip(args, *, had_desktop_app_before_update: bool = False) -> boo _finish_dashboard_update_cleanup(node_failures) with _best_effort('Update receipt finalize (zip path) failed: %s'): from hermes_cli.update_receipt import finalize_update_receipt - finalize_update_receipt("success" if update_complete and not node_failures else "partial") return update_complete diff --git a/hermes_cli/update_lock.py b/hermes_cli/update_lock.py index 36afab7a12..871093acd7 100644 --- a/hermes_cli/update_lock.py +++ b/hermes_cli/update_lock.py @@ -44,7 +44,6 @@ def update_marker_path() -> Path: value into the updater's env, so a profile-scoped path would be one the other owners never look at. """ from hermes_constants import get_process_hermes_home - return get_process_hermes_home() / MARKER_NAME @@ -60,7 +59,6 @@ def _pid_alive(pid: int) -> bool: return False try: from gateway.status import _pid_exists - return bool(_pid_exists(pid)) except Exception as exc: logger.debug("Could not probe pid %s: %s", pid, exc) @@ -88,7 +86,6 @@ def _is_ancestor_pid(pid: int) -> bool: return False try: import psutil - return any(parent.pid == pid for parent in psutil.Process().parents()) except Exception as exc: logger.debug("Could not walk process ancestry for pid %s: %s", pid, exc)