From 0428945b5b07f430e23b4fc28b2bff6887477463 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Sun, 14 Jun 2026 03:08:52 -0700 Subject: [PATCH] fix(desktop): keep profile homes out of bootstrap (#46073) --- apps/desktop/electron/backend-env.cjs | 11 +++++++ apps/desktop/electron/backend-env.test.cjs | 16 ++++++++++ apps/desktop/electron/main.cjs | 4 +-- hermes_cli/setup.py | 11 +++++-- tests/hermes_cli/test_telegram_managed_bot.py | 32 +++++++++++++++++++ 5 files changed, 69 insertions(+), 5 deletions(-) diff --git a/apps/desktop/electron/backend-env.cjs b/apps/desktop/electron/backend-env.cjs index d3b65f4f781..76329785be4 100644 --- a/apps/desktop/electron/backend-env.cjs +++ b/apps/desktop/electron/backend-env.cjs @@ -67,6 +67,16 @@ function buildDesktopBackendPath({ ) } +function normalizeHermesHomeRoot(hermesHome, { pathModule = pathModuleForPlatform(process.platform) } = {}) { + if (!hermesHome) return hermesHome + const resolved = pathModule.resolve(String(hermesHome)) + const parent = pathModule.dirname(resolved) + if (pathModule.basename(parent).toLowerCase() === 'profiles') { + return pathModule.dirname(parent) + } + return resolved +} + function buildDesktopBackendEnv({ hermesHome, pythonPathEntries = [], @@ -97,5 +107,6 @@ module.exports = { buildDesktopBackendEnv, buildDesktopBackendPath, delimiterForPlatform, + normalizeHermesHomeRoot, pathEnvKey } diff --git a/apps/desktop/electron/backend-env.test.cjs b/apps/desktop/electron/backend-env.test.cjs index 1011161917a..75e0c79d5d6 100644 --- a/apps/desktop/electron/backend-env.test.cjs +++ b/apps/desktop/electron/backend-env.test.cjs @@ -7,6 +7,7 @@ const { appendUniquePathEntries, buildDesktopBackendEnv, buildDesktopBackendPath, + normalizeHermesHomeRoot, pathEnvKey } = require('./backend-env.cjs') @@ -66,6 +67,21 @@ test('buildDesktopBackendEnv extends PYTHONPATH and backend PATH together', () = assert.ok(env.PATH.includes('/opt/homebrew/bin')) }) +test('normalizeHermesHomeRoot maps profile homes back to the global Hermes root', () => { + assert.equal( + normalizeHermesHomeRoot('/Users/test/.hermes/profiles/oracle', { pathModule: path.posix }), + '/Users/test/.hermes' + ) + assert.equal( + normalizeHermesHomeRoot('C:\\Users\\test\\AppData\\Local\\hermes\\profiles\\oracle', { pathModule: path.win32 }), + 'C:\\Users\\test\\AppData\\Local\\hermes' + ) + assert.equal( + normalizeHermesHomeRoot('/Users/test/.hermes', { pathModule: path.posix }), + '/Users/test/.hermes' + ) +}) + test('Windows PATH casing and delimiter are preserved without POSIX sane entries', () => { const env = buildDesktopBackendEnv({ hermesHome: 'C:\\Users\\test\\AppData\\Local\\hermes', diff --git a/apps/desktop/electron/main.cjs b/apps/desktop/electron/main.cjs index 64008d06e79..c714a46ee46 100644 --- a/apps/desktop/electron/main.cjs +++ b/apps/desktop/electron/main.cjs @@ -38,7 +38,7 @@ const { adoptServedDashboardToken } = require('./dashboard-token.cjs') const { waitForDashboardPort } = require('./backend-ready.cjs') const { serializeJsonBody, setJsonRequestHeaders } = require('./oauth-net-request.cjs') const { fetchMarketplaceThemes, searchMarketplaceThemes } = require('./vscode-marketplace.cjs') -const { buildDesktopBackendEnv } = require('./backend-env.cjs') +const { buildDesktopBackendEnv, normalizeHermesHomeRoot } = require('./backend-env.cjs') const { readDirForIpc } = require('./fs-read-dir.cjs') const { gitRootForIpc } = require('./git-root.cjs') const { worktreesForIpc } = require('./git-worktrees.cjs') @@ -240,7 +240,7 @@ if (INSTALL_STAMP) { // HERMES_HOME beneath the throwaway userData dir so a fresh-install run never // touches the user's real ~/.hermes / %LOCALAPPDATA%\hermes. function resolveHermesHome() { - if (process.env.HERMES_HOME) return path.resolve(process.env.HERMES_HOME) + if (process.env.HERMES_HOME) return normalizeHermesHomeRoot(process.env.HERMES_HOME) if (USER_DATA_OVERRIDE) return path.join(path.resolve(USER_DATA_OVERRIDE), 'hermes-home') if (IS_WINDOWS && process.env.LOCALAPPDATA) { const localappdata = path.join(process.env.LOCALAPPDATA, 'hermes') diff --git a/hermes_cli/setup.py b/hermes_cli/setup.py index 266eb9eaa39..75bde2a93c4 100644 --- a/hermes_cli/setup.py +++ b/hermes_cli/setup.py @@ -1655,15 +1655,20 @@ def _setup_telegram_auto_result(): profile_name: str | None = None try: - hermes_home = str(get_hermes_home()) - if "/profiles/" in hermes_home: - profile_name = hermes_home.rstrip("/").rsplit("/", 1)[-1] + profile_name = _profile_name_from_hermes_home(Path(get_hermes_home())) except Exception: pass return auto_setup_telegram_bot_result(profile_name=profile_name) +def _profile_name_from_hermes_home(hermes_home) -> str | None: + """Return the active profile name when HERMES_HOME is a profile dir.""" + if hermes_home.parent.name == "profiles": + return hermes_home.name + return None + + def _setup_telegram_auto() -> str | None: """Attempt automatic Telegram bot creation and return only the token.""" result = _setup_telegram_auto_result() diff --git a/tests/hermes_cli/test_telegram_managed_bot.py b/tests/hermes_cli/test_telegram_managed_bot.py index 1fa0ebfe014..a23f8c31fce 100644 --- a/tests/hermes_cli/test_telegram_managed_bot.py +++ b/tests/hermes_cli/test_telegram_managed_bot.py @@ -2,6 +2,7 @@ from __future__ import annotations +from pathlib import PureWindowsPath from unittest.mock import MagicMock, patch from hermes_cli.telegram_managed_bot import ( @@ -321,3 +322,34 @@ class TestSetupTelegramAuto: from hermes_cli.setup import _setup_telegram_auto assert callable(_setup_telegram_auto) + + def test_setup_result_passes_profile_name_for_profile_home(self, monkeypatch, tmp_path): + from hermes_cli import setup + + seen = {} + profile_home = tmp_path / ".hermes" / "profiles" / "oracle" + profile_home.mkdir(parents=True) + + monkeypatch.setattr(setup, "get_hermes_home", lambda: profile_home) + + def fake_auto_setup_telegram_bot_result(*, profile_name=None): + seen["profile_name"] = profile_name + return None + + monkeypatch.setattr( + "hermes_cli.telegram_managed_bot.auto_setup_telegram_bot_result", + fake_auto_setup_telegram_bot_result, + ) + + assert setup._setup_telegram_auto_result() is None + assert seen["profile_name"] == "oracle" + + def test_profile_name_from_home_path_handles_windows_separators(self): + from hermes_cli.setup import _profile_name_from_hermes_home + + assert ( + _profile_name_from_hermes_home( + PureWindowsPath(r"C:\Users\test\AppData\Local\hermes\profiles\oracle") + ) + == "oracle" + )