From 1653a04f70585a08cd10775a6c340ccb44ecf0fa Mon Sep 17 00:00:00 2001 From: emozilla Date: Fri, 29 May 2026 02:18:31 -0400 Subject: [PATCH] fix(gui): pin /api/hermes/update to the current branch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The desktop command-center 'update' action hits POST /api/hermes/update, which spawned bare `hermes update` with no --branch. cmd_update then falls back to its default (main) and checks the working tree OUT of the tracked branch — a bb/gui install silently jumped to main and lost the desktop CLI. Resolve the checkout's current branch and pass --branch from this endpoint only. The engine default (main) is DELIBERATELY unchanged: bare `hermes update` from a terminal, the gateway /update bot command, and the CLI/TUI relaunch path all keep their long-standing 'update against main' contract for the existing user base. Only the GUI button is scoped to update-the-branch-you're-on. Detached HEAD / git failure falls back to the bare default. --- hermes_cli/web_server.py | 42 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py index 11bd2c621cf..be05fc4be7b 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -1058,6 +1058,32 @@ _ACTION_PROCS: Dict[str, subprocess.Popen] = {} _ACTION_SPAWN_LOCK = threading.Lock() +def _current_git_branch() -> Optional[str]: + """Return the branch PROJECT_ROOT is on, or None if detached/unknown. + + Used to pin ``hermes update`` to the tracked branch instead of letting it + default to main. Best-effort: returns None on detached HEAD, non-git + checkouts, or any git failure, in which case callers fall back to the + bare ``hermes update`` default. + """ + try: + result = subprocess.run( + ["git", "rev-parse", "--abbrev-ref", "HEAD"], + cwd=str(PROJECT_ROOT), + capture_output=True, + text=True, + timeout=10, + ) + except Exception: + return None + if result.returncode != 0: + return None + branch = result.stdout.strip() + if not branch or branch == "HEAD": # empty or detached + return None + return branch + + def _spawn_hermes_action(subcommand: List[str], name: str) -> subprocess.Popen: """Spawn ``hermes `` detached and record the Popen handle. @@ -1146,9 +1172,21 @@ async def restart_gateway(): @app.post("/api/hermes/update") async def update_hermes(): - """Kick off ``hermes update`` in the background.""" + """Kick off ``hermes update`` in the background. + + Pin the update to the branch the checkout is CURRENTLY on. Without this, + ``hermes update`` falls back to its built-in default (main) and switches + the working tree off the tracked branch — e.g. a bb/gui install silently + jumps to main and loses the desktop CLI. Pass --branch so the + update stays on-branch. Detached HEAD / detection failure falls back to + bare ``hermes update`` (the prior behavior). + """ + args: List[str] = ["update"] + branch = _current_git_branch() + if branch: + args += ["--branch", branch] try: - proc = _spawn_hermes_action(["update"], "hermes-update") + proc = _spawn_hermes_action(args, "hermes-update") except Exception as exc: _log.exception("Failed to spawn hermes update") raise HTTPException(status_code=500, detail=f"Failed to start update: {exc}")