From f14280e2c432e67990b7605d4eb74a7346ada422 Mon Sep 17 00:00:00 2001 From: bergeouss Date: Thu, 30 Apr 2026 23:24:31 +0000 Subject: [PATCH] fix(#1195): route sessions to profile dir even when dir doesn't exist yet (#1373) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a user switched profiles and created a new session, the session was saved to the default profile directory instead of the active profile directory — because get_hermes_home_for_profile() silently fell back to _DEFAULT_HERMES_HOME when the profile directory didn't exist yet on disk. Root cause: api/profiles.py:156 had `if profile_dir.is_dir(): return profile_dir; return _DEFAULT_HERMES_HOME`. New profiles (no session yet, so no dir) routed every session back to default. Fix: remove the is_dir() guard, return the profile path unconditionally. The profile directory is created on first use by the agent/session layer. 5 regression tests in tests/test_issue1195_session_profile_routing.py: existing-profile, non-existent-profile (the core fix), None, empty- string, 'default' all return the expected path. Co-authored-by: bergeouss --- api/profiles.py | 4 +- docs/ISSUES.md | 23 ++++++ .../test_issue1195_session_profile_routing.py | 81 +++++++++++++++++++ 3 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 docs/ISSUES.md create mode 100644 tests/test_issue1195_session_profile_routing.py diff --git a/api/profiles.py b/api/profiles.py index a3965991..f87ac985 100644 --- a/api/profiles.py +++ b/api/profiles.py @@ -153,9 +153,7 @@ def get_hermes_home_for_profile(name: str) -> Path: if not name or name == 'default' or not _PROFILE_ID_RE.match(name): return _DEFAULT_HERMES_HOME profile_dir = _DEFAULT_HERMES_HOME / 'profiles' / name - if profile_dir.is_dir(): - return profile_dir - return _DEFAULT_HERMES_HOME + return profile_dir _TERMINAL_ENV_MAPPINGS = { diff --git a/docs/ISSUES.md b/docs/ISSUES.md new file mode 100644 index 00000000..e3f65641 --- /dev/null +++ b/docs/ISSUES.md @@ -0,0 +1,23 @@ +# Upstream Issues — Root Cause Analysis + +## #1256: Browser tools fail with "Playwright not installed" + +### Root Cause +The check lives in **hermes-agent** (upstream), not hermes-webui: + +``` +hermes-agent/tools/browser_tool.py → check_browser_requirements() +``` + +`check_browser_requirements()` does not recognize CDP (Chrome DevTools Protocol) mode — it only looks for a local Playwright/Puppeteer install. When the agent runs in CDP mode (connecting to an existing browser), the check still fails. + +### WebUI side +The WebUI already passes `CLI_TOOLSETS` correctly per-request. The `enabled_toolsets` field in the cron/chat config is dynamic and works as intended. + +### Fix required +The fix must happen in `hermes-agent/tools/browser_tool.py`: +- `check_browser_requirements()` should skip the Playwright check when CDP mode is configured +- Or add a `BROWSER_MODE=cdp` env var that bypasses the local browser requirement + +### Workaround +Use `CLOUD_BROWSER=true` or configure `browser.base_url` to point to a remote CDP endpoint. This bypasses the local Playwright requirement. diff --git a/tests/test_issue1195_session_profile_routing.py b/tests/test_issue1195_session_profile_routing.py new file mode 100644 index 00000000..534a3f73 --- /dev/null +++ b/tests/test_issue1195_session_profile_routing.py @@ -0,0 +1,81 @@ +"""Tests for issue #1195: sessions must route to the correct profile directory +even when that profile directory does not exist yet on disk.""" + +import os +import tempfile +from pathlib import Path +from unittest.mock import patch + +import pytest + + +# ── helpers ────────────────────────────────────────────────────────────────── + +def _make_hermes_home(base: Path, profile_name: str | None = None) -> Path: + """Create a temp HERMES_HOME (with optional profile dir) and return it.""" + hermes_home = base / ".hermes" + hermes_home.mkdir(parents=True, exist_ok=True) + if profile_name: + (hermes_home / "profiles" / profile_name).mkdir(parents=True, exist_ok=True) + return hermes_home + + +# ── tests ──────────────────────────────────────────────────────────────────── + +class TestGetHermesHomeForProfile: + """get_hermes_home_for_profile() must return the profile path regardless of + whether the directory already exists on disk (#1195).""" + + @pytest.fixture(autouse=True) + def _patch_default_home(self, tmp_path): + """Patch _DEFAULT_HERMES_HOME to a temp directory for isolation.""" + from api.profiles import _DEFAULT_HERMES_HOME as real_default + + fake_home = tmp_path / ".hermes" + fake_home.mkdir(parents=True) + with patch("api.profiles._DEFAULT_HERMES_HOME", fake_home): + yield fake_home, real_default + + def test_existing_profile_returns_profile_dir(self, _patch_default_home): + fake_home, _ = _patch_default_home + from api.profiles import get_hermes_home_for_profile + + # Create an existing profile directory + profile_dir = fake_home / "profiles" / "ayan" + profile_dir.mkdir(parents=True) + + result = get_hermes_home_for_profile("ayan") + assert result == profile_dir + + def test_nonexistent_profile_still_returns_profile_path(self, _patch_default_home): + """Core bug fix: profile dir doesn't exist yet but should still route there.""" + fake_home, _ = _patch_default_home + from api.profiles import get_hermes_home_for_profile + + # Do NOT create the profile directory + expected = fake_home / "profiles" / "newprofile" + assert not expected.exists() # confirm it doesn't exist + + result = get_hermes_home_for_profile("newprofile") + assert result == expected, "Should route to profile path even when dir missing" + + def test_none_returns_default(self, _patch_default_home): + fake_home, _ = _patch_default_home + from api.profiles import get_hermes_home_for_profile + + result = get_hermes_home_for_profile(None) + assert result == fake_home + + def test_empty_string_returns_default(self, _patch_default_home): + fake_home, _ = _patch_default_home + from api.profiles import get_hermes_home_for_profile + + result = get_hermes_home_for_profile("") + assert result == fake_home + + def test_default_string_returns_default(self, _patch_default_home): + fake_home, _ = _patch_default_home + from api.profiles import get_hermes_home_for_profile + + result = get_hermes_home_for_profile("default") + assert result == fake_home