Merge pull request #4596 from nesquena/stage-4589
Release v0.51.549 — HOTFIX profile switching for single-user named profiles (#4589/#4586)
This commit is contained in:
4 files changed
+326
-25
No files matched your search
@@ -3,6 +3,12 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v0.51.549] — 2026-06-21 — Release TH (hotfix: profile switching restored for single-user named profiles)
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Profile switching works again for normal single-user named profiles (hotfix #4586).** A regression (since v0.51.528) wrongly treated any single user running under a named profile as an isolated multi-user deployment — because isolated mode was inferred from the `HERMES_HOME` path shape (`*/profiles/<name>`), which the agent launcher exports for any named profile. The Profiles tab showed only one profile and switching was disabled. Isolated single-profile mode now requires an explicit `HERMES_WEBUI_ISOLATED_PROFILE` opt-in and is never inferred from the path shape. The opt-in is also protected from being overridden by a profile's own `.env` (on both the live-env and runtime/background-worker paths), so a pinned profile can't disable its own isolation. Thanks @nesquena-hermes.
|
||||
|
||||
## [v0.51.548] — 2026-06-21 — Release TG (extension load diagnostics)
|
||||
|
||||
### Added
|
||||
|
||||
+83
-20
@@ -125,41 +125,85 @@ def _unwrap_profile_home_to_base(home: Path) -> Path:
|
||||
return home
|
||||
|
||||
|
||||
def _is_isolated_profile_mode() -> bool:
|
||||
"""Detect isolated single-profile mode from HERMES_HOME env var.
|
||||
# Env keys a pinned profile's .env may NOT override via _reload_dotenv() — these
|
||||
# are operator/deployment-level postures, not per-profile toggles. Letting a
|
||||
# profile .env set HERMES_WEBUI_ISOLATED_PROFILE=0 would let a contained user
|
||||
# escape isolation (#4589).
|
||||
_PROTECTED_ENV_KEYS = frozenset({'HERMES_WEBUI_ISOLATED_PROFILE'})
|
||||
|
||||
Returns True when HERMES_HOME points at a concrete profile subdirectory
|
||||
(e.g., ~/.hermes/profiles/user1) rather than the base home (~/.hermes).
|
||||
This indicates a single-profile deployment where the WebUI should pin to
|
||||
that profile and reject cross-profile operations.
|
||||
|
||||
Isolation is derived only from the literal ``*/profiles/<name>`` shape of
|
||||
HERMES_HOME at startup. A normal multi-profile deployment points
|
||||
HERMES_HOME at the base home (``~/.hermes``), which does not match the
|
||||
shape, so isolation stays off. We deliberately do not key this off
|
||||
HERMES_BASE_HOME: that variable normally points at the base home already,
|
||||
so using it as an opt-out silently disables legitimate isolated-profile
|
||||
deployments.
|
||||
def _isolated_profile_opt_in() -> bool:
|
||||
"""Return True only when isolated single-profile mode is EXPLICITLY enabled.
|
||||
Isolated mode is an intentional multi-user deployment posture (each user is
|
||||
pinned to one profile and cross-profile operations are rejected). It must be
|
||||
opted into with ``HERMES_WEBUI_ISOLATED_PROFILE`` — it is NEVER inferred from
|
||||
the ``HERMES_HOME`` shape alone, because a normal single-user who runs under a
|
||||
named profile produces the byte-identical ``*/profiles/<name>`` shape (the
|
||||
Hermes Agent launcher exports ``HERMES_HOME=~/.hermes/profiles/<name>`` for any
|
||||
active named profile). Keying isolation off the shape alone therefore breaks
|
||||
profile switching for ordinary single-user deployments (#4586).
|
||||
|
||||
Uses _INITIAL_HERMES_HOME (snapshotted at import time) to detect isolation,
|
||||
not the current os.environ value. init_profile_state() overwrites HERMES_HOME
|
||||
at startup, which would disable isolation detection if we read it here.
|
||||
Accepts the usual truthy values; default (unset/empty/falsey) is OFF.
|
||||
|
||||
Security: this reads the operator-set env var live, but a pinned profile's
|
||||
``.env`` can NEVER override it — ``_reload_dotenv()`` explicitly refuses to
|
||||
copy ``HERMES_WEBUI_ISOLATED_PROFILE`` from a profile ``.env`` into
|
||||
``os.environ`` (see ``_PROTECTED_ENV_KEYS``). Otherwise a contained user could
|
||||
set ``HERMES_WEBUI_ISOLATED_PROFILE=0`` in their own profile ``.env`` and
|
||||
silently escape isolation (#4589). The opt-in is an operator/deployment
|
||||
posture, never a per-profile-file toggle.
|
||||
"""
|
||||
return os.getenv('HERMES_WEBUI_ISOLATED_PROFILE', '').strip().lower() in (
|
||||
'1', 'true', 'yes', 'on',
|
||||
)
|
||||
|
||||
|
||||
def _is_isolated_profile_mode() -> bool:
|
||||
"""Detect isolated single-profile mode.
|
||||
|
||||
Returns True only when BOTH conditions hold:
|
||||
1. ``HERMES_WEBUI_ISOLATED_PROFILE`` is explicitly enabled (the PRIMARY
|
||||
gate — see _isolated_profile_opt_in), AND
|
||||
2. HERMES_HOME at startup points at a concrete profile subdirectory
|
||||
(e.g., ~/.hermes/profiles/user1) rather than the base home.
|
||||
|
||||
Why the explicit flag is required (#4586 regression fix): the
|
||||
``*/profiles/<name>`` shape alone CANNOT distinguish an intentional
|
||||
multi-user isolation deployment from an ordinary single-user running under a
|
||||
named profile — the Hermes Agent launcher sets
|
||||
``HERMES_HOME=~/.hermes/profiles/<name>`` for any active named profile, so the
|
||||
two cases are byte-identical at the env-var level. Inferring isolation from
|
||||
the shape alone (the v0.51.528 behaviour from #2698) wrongly pinned ordinary
|
||||
single-user deployments to one profile and disabled profile switching. The
|
||||
multi-user wrapper that genuinely wants isolation now sets the explicit flag;
|
||||
everyone else is never caught. The shape stays as a secondary requirement so
|
||||
a stray flag without a profile-shaped HERMES_HOME does not engage isolation.
|
||||
|
||||
Uses _INITIAL_HERMES_HOME (snapshotted at import time) to detect the shape,
|
||||
not the current os.environ value. init_profile_state() overwrites HERMES_HOME
|
||||
at startup, which would disable detection if we read it here.
|
||||
"""
|
||||
# PRIMARY gate: explicit opt-in. Default OFF → a normal named-profile launch
|
||||
# is never treated as isolated, so profile switching keeps working (#4586).
|
||||
if not _isolated_profile_opt_in():
|
||||
return False
|
||||
|
||||
hermes_home = _INITIAL_HERMES_HOME
|
||||
if not hermes_home:
|
||||
return False
|
||||
|
||||
p = Path(hermes_home).expanduser()
|
||||
# Check if this path looks like ~/.hermes/profiles/<name>
|
||||
# i.e., parent dir is named 'profiles' and grandparent exists
|
||||
# SECONDARY requirement: HERMES_HOME must look like ~/.hermes/profiles/<name>
|
||||
# i.e., parent dir is named 'profiles' and grandparent exists.
|
||||
if p.parent.name == 'profiles' and p.parent.parent.exists():
|
||||
return True
|
||||
if p.is_symlink():
|
||||
global _ISOLATED_SYMLINK_WARNING_EMITTED
|
||||
if not _ISOLATED_SYMLINK_WARNING_EMITTED:
|
||||
logger.warning(
|
||||
"HERMES_HOME symlink %s does not literally match */profiles/<name>; "
|
||||
"isolated profile mode is disabled unless the literal profile path is used.",
|
||||
"HERMES_WEBUI_ISOLATED_PROFILE is set but HERMES_HOME %s does not "
|
||||
"literally match */profiles/<name>; isolated profile mode stays off "
|
||||
"unless the literal profile path is used.",
|
||||
p,
|
||||
)
|
||||
_ISOLATED_SYMLINK_WARNING_EMITTED = True
|
||||
@@ -770,6 +814,12 @@ def get_profile_runtime_env(home: Path) -> dict[str, str]:
|
||||
k = k.strip()
|
||||
v = v.strip().strip('"').strip("'")
|
||||
if k and v:
|
||||
# #4589: never let a profile's own .env override an
|
||||
# operator/deployment posture (e.g. disable isolation via
|
||||
# HERMES_WEBUI_ISOLATED_PROFILE=0) on the runtime-env path
|
||||
# the same way _reload_dotenv() protects the live env.
|
||||
if k in _PROTECTED_ENV_KEYS:
|
||||
continue
|
||||
env[k] = v
|
||||
except Exception:
|
||||
logger.debug("Failed to read runtime env from %s", env_path)
|
||||
@@ -793,6 +843,9 @@ _BLOCKED_RUNTIME_ENV_KEYS = {
|
||||
'PYTHONPATH',
|
||||
'VIRTUAL_ENV',
|
||||
'LD_LIBRARY_PATH',
|
||||
# #4589: operator/deployment isolation posture — never overridable by a
|
||||
# profile's own env on any runtime/gateway-parity path.
|
||||
'HERMES_WEBUI_ISOLATED_PROFILE',
|
||||
}
|
||||
|
||||
|
||||
@@ -1031,6 +1084,16 @@ def _reload_dotenv(home: Path):
|
||||
k = k.strip()
|
||||
v = v.strip().strip('"').strip("'")
|
||||
if k and v:
|
||||
# Operator/deployment-level keys are never overridable by a
|
||||
# profile's own .env (#4589 — prevents a contained user from
|
||||
# disabling their isolation via HERMES_WEBUI_ISOLATED_PROFILE=0).
|
||||
if k in _PROTECTED_ENV_KEYS:
|
||||
logger.warning(
|
||||
"Ignoring protected key %s in profile .env %s; "
|
||||
"operator/deployment env takes precedence",
|
||||
k, env_path,
|
||||
)
|
||||
continue
|
||||
os.environ[k] = v
|
||||
loaded_keys.add(k)
|
||||
_loaded_profile_env_keys = loaded_keys
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
"""
|
||||
Tests for issue #2698: HERMES_HOME isolated profile mode.
|
||||
|
||||
When HERMES_HOME points at a specific profile directory like ~/.hermes/profiles/user1,
|
||||
the WebUI should pin to that single profile: list only it, reject create/switch/delete
|
||||
of other profiles, and hide multi-profile UI affordances.
|
||||
When HERMES_HOME points at a specific profile directory like ~/.hermes/profiles/user1
|
||||
AND isolated mode is explicitly opted into via HERMES_WEBUI_ISOLATED_PROFILE=1, the WebUI
|
||||
should pin to that single profile: list only it, reject create/switch/delete of other
|
||||
profiles, and hide multi-profile UI affordances.
|
||||
|
||||
Note (#4586): isolated mode now requires the explicit HERMES_WEBUI_ISOLATED_PROFILE opt-in
|
||||
in addition to the profile-shaped HERMES_HOME — the shape alone is NOT sufficient, because a
|
||||
normal single-user named profile produces the same shape. The autouse fixture below enables
|
||||
the flag for this whole module (it tests the isolated-mode deployment posture); the
|
||||
shape-without-flag regression is covered separately in test_issue4586_*.
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -32,8 +39,15 @@ from api.profiles import (
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _clear_profile_cache():
|
||||
"""Clear the profile list cache before and after each test to prevent cross-test leakage."""
|
||||
def _clear_profile_cache(monkeypatch):
|
||||
"""Clear the profile list cache + enable the isolated-mode opt-in for every test.
|
||||
|
||||
#4586: isolated mode is gated on the explicit HERMES_WEBUI_ISOLATED_PROFILE flag, so
|
||||
this whole module — which exercises isolated-mode behavior — enables it. Normal-mode
|
||||
assertions in this file still hold because they point HERMES_HOME at the base home, which
|
||||
fails the secondary shape requirement regardless of the flag.
|
||||
"""
|
||||
monkeypatch.setenv("HERMES_WEBUI_ISOLATED_PROFILE", "1")
|
||||
_profiles_mod._LIST_PROFILES_CACHE = None
|
||||
yield
|
||||
_profiles_mod._LIST_PROFILES_CACHE = None
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
"""
|
||||
Regression test for issue #4586: the v0.51.528 isolated-profile-mode feature (#2698)
|
||||
must NOT engage from the HERMES_HOME *shape* alone.
|
||||
|
||||
The bug: `_is_isolated_profile_mode()` inferred "isolated mode" purely from a
|
||||
`~/.hermes/profiles/<name>` shaped HERMES_HOME. But the Hermes Agent launcher exports
|
||||
exactly that shape for ANY active named (non-default) profile in a normal single-user
|
||||
deployment — so an ordinary single user running under a named profile (e.g. `webui`) was
|
||||
wrongly treated as an intentional multi-user isolation deployment. Symptoms (v0.51.528+):
|
||||
- the Profiles tab listed only the active profile, and
|
||||
- switching to any other profile was blocked with PermissionError
|
||||
("Profile switching is not allowed in isolated profile mode.").
|
||||
|
||||
The fix (#4586): isolated mode requires an EXPLICIT opt-in — HERMES_WEBUI_ISOLATED_PROFILE —
|
||||
as the primary gate (default OFF). The profile-shaped HERMES_HOME is only a secondary
|
||||
requirement. A normal named-profile launch (shape set, flag unset) must therefore stay in
|
||||
normal multi-profile mode.
|
||||
|
||||
These tests deliberately set ONLY the profile shape (no flag) and assert NORMAL behavior —
|
||||
the exact thing that regressed. They would FAIL on v0.51.528..current and PASS after the fix.
|
||||
"""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
import api.profiles as _profiles_mod
|
||||
from api.profiles import (
|
||||
_is_isolated_profile_mode,
|
||||
_isolated_profile_opt_in,
|
||||
switch_profile,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _clear_cache_and_force_flag_off(monkeypatch):
|
||||
"""Every test here runs WITHOUT the isolated opt-in — the regressed single-user case.
|
||||
|
||||
We explicitly clear HERMES_WEBUI_ISOLATED_PROFILE so the suite's ambient env can't
|
||||
accidentally enable isolation and mask the regression.
|
||||
"""
|
||||
monkeypatch.delenv("HERMES_WEBUI_ISOLATED_PROFILE", raising=False)
|
||||
_profiles_mod._LIST_PROFILES_CACHE = None
|
||||
yield
|
||||
_profiles_mod._LIST_PROFILES_CACHE = None
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def named_profile_home():
|
||||
"""A normal single-user layout: base ~/.hermes with several profiles, active = `webui`.
|
||||
|
||||
The active profile's home is `~/.hermes/profiles/webui` — exactly the
|
||||
`*/profiles/<name>` shape the Hermes Agent launcher exports for a named profile, and
|
||||
exactly what the #4586 false-positive keyed off.
|
||||
"""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
home = Path(tmpdir) / ".hermes"
|
||||
profiles_root = home / "profiles"
|
||||
profiles_root.mkdir(parents=True)
|
||||
for name in ("alpha", "beta", "webui"):
|
||||
p = profiles_root / name
|
||||
for subdir in ("memories", "sessions", "skills", "skins",
|
||||
"logs", "plans", "workspace", "cron"):
|
||||
(p / subdir).mkdir(parents=True, exist_ok=True)
|
||||
yield {"base": home, "active": profiles_root / "webui", "profiles_root": profiles_root}
|
||||
|
||||
|
||||
class TestIssue4586NamedProfileIsNotIsolated:
|
||||
"""A named-profile single-user launch (shape set, flag unset) is NOT isolated mode."""
|
||||
|
||||
def test_shape_alone_does_not_enable_isolated_mode(self, named_profile_home):
|
||||
"""The core regression: */profiles/<name> WITHOUT the flag → NOT isolated."""
|
||||
active = named_profile_home["active"]
|
||||
assert active.parent.name == "profiles" # has the shape that used to false-positive
|
||||
with mock.patch.dict(os.environ, {"HERMES_HOME": str(active)}, clear=False):
|
||||
with mock.patch("api.profiles._INITIAL_HERMES_HOME", str(active)):
|
||||
assert _isolated_profile_opt_in() is False
|
||||
assert _is_isolated_profile_mode() is False, (
|
||||
"named-profile shape must NOT engage isolated mode without the explicit "
|
||||
"HERMES_WEBUI_ISOLATED_PROFILE opt-in (#4586)"
|
||||
)
|
||||
|
||||
def test_profiles_tab_is_not_clamped_to_single_profile(self, named_profile_home):
|
||||
"""Regressed symptom #1: Profiles tab showed only the active profile.
|
||||
|
||||
The regression was driven entirely by `list_profiles_api()` taking its
|
||||
isolated-mode early-return (`if _is_isolated_profile_mode(): return [only active]`).
|
||||
We assert the GATE that controls that branch is off — environment-independent,
|
||||
unlike exercising the full hermes_cli-backed discovery machinery (which depends on
|
||||
real base-home/profiles-root dirs that differ under CI). With the gate off,
|
||||
list_profiles_api() takes its normal multi-profile path instead of clamping to one.
|
||||
"""
|
||||
active = named_profile_home["active"]
|
||||
with mock.patch.dict(os.environ, {"HERMES_HOME": str(active)}, clear=False):
|
||||
with mock.patch("api.profiles._INITIAL_HERMES_HOME", str(active)):
|
||||
# The isolated early-return in list_profiles_api() is gated on this:
|
||||
assert _is_isolated_profile_mode() is False, (
|
||||
"list_profiles_api() must NOT take its single-profile early-return for "
|
||||
"a normal named profile — that early-return is what hid all but the "
|
||||
"active profile in the Profiles tab (#4586)"
|
||||
)
|
||||
|
||||
def test_switching_to_another_profile_is_allowed(self, named_profile_home):
|
||||
"""Regressed symptom #2: switching was blocked with PermissionError."""
|
||||
base = named_profile_home["base"]
|
||||
active = named_profile_home["active"]
|
||||
with mock.patch.dict(os.environ, {"HERMES_HOME": str(active)}, clear=False):
|
||||
with mock.patch("api.profiles._INITIAL_HERMES_HOME", str(active)):
|
||||
with mock.patch("api.profiles._DEFAULT_HERMES_HOME", base):
|
||||
with mock.patch("api.profiles._resolve_base_hermes_home", return_value=base):
|
||||
# Must NOT raise PermissionError (the regression). process_wide=False
|
||||
# keeps this from mutating global interpreter state during the test.
|
||||
try:
|
||||
switch_profile("alpha", process_wide=False)
|
||||
except PermissionError as e:
|
||||
pytest.fail(
|
||||
f"profile switching must work for a normal named profile; "
|
||||
f"got PermissionError: {e} (#4586)"
|
||||
)
|
||||
|
||||
|
||||
class TestIssue4586ExplicitOptInStillWorks:
|
||||
"""The explicit opt-in still engages isolated mode (multi-user deployments unaffected)."""
|
||||
|
||||
@pytest.mark.parametrize("flag", ["1", "true", "TRUE", "yes", "on"])
|
||||
def test_flag_plus_shape_enables_isolated_mode(self, named_profile_home, flag):
|
||||
active = named_profile_home["active"]
|
||||
with mock.patch.dict(os.environ, {"HERMES_HOME": str(active),
|
||||
"HERMES_WEBUI_ISOLATED_PROFILE": flag}, clear=False):
|
||||
with mock.patch("api.profiles._INITIAL_HERMES_HOME", str(active)):
|
||||
assert _isolated_profile_opt_in() is True
|
||||
assert _is_isolated_profile_mode() is True, (
|
||||
f"explicit opt-in ({flag!r}) + profile shape must still engage isolated mode"
|
||||
)
|
||||
|
||||
def test_flag_without_profile_shape_stays_off(self, named_profile_home):
|
||||
"""Secondary requirement: a stray flag without a profile-shaped HERMES_HOME = OFF."""
|
||||
base = named_profile_home["base"] # base ~/.hermes, NOT a */profiles/<name> path
|
||||
with mock.patch.dict(os.environ, {"HERMES_HOME": str(base),
|
||||
"HERMES_WEBUI_ISOLATED_PROFILE": "1"}, clear=False):
|
||||
with mock.patch("api.profiles._INITIAL_HERMES_HOME", str(base)):
|
||||
assert _is_isolated_profile_mode() is False, (
|
||||
"flag set but HERMES_HOME is the base home → isolation must stay off"
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("flag", ["", "0", "false", "no", "off", " "])
|
||||
def test_falsey_flag_values_are_off(self, named_profile_home, flag):
|
||||
active = named_profile_home["active"]
|
||||
with mock.patch.dict(os.environ, {"HERMES_HOME": str(active),
|
||||
"HERMES_WEBUI_ISOLATED_PROFILE": flag}, clear=False):
|
||||
with mock.patch("api.profiles._INITIAL_HERMES_HOME", str(active)):
|
||||
assert _is_isolated_profile_mode() is False, (
|
||||
f"falsey flag {flag!r} must not engage isolated mode"
|
||||
)
|
||||
|
||||
|
||||
class TestIssue4589ProfileEnvCannotDisableIsolation:
|
||||
"""#4589: a pinned profile's own .env must NOT be able to turn isolation OFF.
|
||||
|
||||
_reload_dotenv() copies a profile's .env into os.environ. Before the fix, a
|
||||
contained user could put HERMES_WEBUI_ISOLATED_PROFILE=0 in their profile .env
|
||||
and escape isolation. The operator flag is now protected from .env override.
|
||||
"""
|
||||
|
||||
def test_profile_env_cannot_clear_isolated_flag(self, named_profile_home):
|
||||
from api.profiles import _reload_dotenv, _PROTECTED_ENV_KEYS
|
||||
|
||||
assert "HERMES_WEBUI_ISOLATED_PROFILE" in _PROTECTED_ENV_KEYS
|
||||
|
||||
active = named_profile_home["active"]
|
||||
# Operator opts the deployment into isolation.
|
||||
with mock.patch.dict(os.environ, {"HERMES_HOME": str(active),
|
||||
"HERMES_WEBUI_ISOLATED_PROFILE": "1"}, clear=False):
|
||||
with mock.patch("api.profiles._INITIAL_HERMES_HOME", str(active)):
|
||||
assert _is_isolated_profile_mode() is True
|
||||
|
||||
# The contained user plants HERMES_WEBUI_ISOLATED_PROFILE=0 in their .env.
|
||||
(active / ".env").write_text(
|
||||
"HERMES_WEBUI_ISOLATED_PROFILE=0\nSOME_OTHER_KEY=ok\n", encoding="utf-8"
|
||||
)
|
||||
try:
|
||||
_reload_dotenv(active)
|
||||
# The protected flag must NOT have been overridden → still isolated.
|
||||
assert os.environ.get("HERMES_WEBUI_ISOLATED_PROFILE") == "1", (
|
||||
"profile .env must not override the operator isolation flag"
|
||||
)
|
||||
assert _is_isolated_profile_mode() is True, (
|
||||
"#4589: a profile .env must not be able to disable isolation"
|
||||
)
|
||||
# Non-protected keys still load normally.
|
||||
assert os.environ.get("SOME_OTHER_KEY") == "ok"
|
||||
finally:
|
||||
try:
|
||||
os.environ.pop("SOME_OTHER_KEY", None)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def test_runtime_env_path_also_strips_protected_flag(self, named_profile_home):
|
||||
"""#4589 (runtime path): get_profile_runtime_env must NOT carry the protected
|
||||
isolation flag out of a profile's .env (the background-worker/streaming path
|
||||
also applies runtime env to os.environ — same escape, second door)."""
|
||||
from api.profiles import get_profile_runtime_env, _BLOCKED_RUNTIME_ENV_KEYS
|
||||
|
||||
assert "HERMES_WEBUI_ISOLATED_PROFILE" in _BLOCKED_RUNTIME_ENV_KEYS
|
||||
|
||||
active = named_profile_home["active"]
|
||||
(active / ".env").write_text(
|
||||
"HERMES_WEBUI_ISOLATED_PROFILE=0\nMY_PROFILE_KEY=value1\n", encoding="utf-8"
|
||||
)
|
||||
env = get_profile_runtime_env(active)
|
||||
assert "HERMES_WEBUI_ISOLATED_PROFILE" not in env, (
|
||||
"#4589: runtime env must not carry the isolation flag out of a profile .env"
|
||||
)
|
||||
# Non-protected profile keys still project into runtime env.
|
||||
assert env.get("MY_PROFILE_KEY") == "value1"
|
||||
Reference in new issue
Block a user