# NousResearch/hermes-agent · Active profile not propagated to desktop/TUI subsystems

## Open now

- Nothing open on GitHub.

Multiple bugs and fixes around desktop, TUI, and dashboard components ignoring the active profile and falling back to global or default settings. Affected areas include TTS/voice config, audio endpoints, chat workers, skills resolution, and slash-command subprocesses.

## TL;DR

- 15 member(s): 0 open issue(s), 6 closed issue(s), 0 open PR(s), 2 merged PR(s), 7 PR(s) closed without merging.
- A fix was merged to main in PR #60180. Verify it addresses your case.
- **Recommended:** read PR #60180 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `partially_resolved` · deciding PR(s): `pr-60180,pr-73521`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_PARTIAL`): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
- Members outside that structural core may belong to a different underlying problem.
- Pairwise link density `0.11`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #40677: \[Bug\]: Profile-local skills are unavailable in Dashboard/TUI/Desktop GUI because child processes use the root HERMES_HOME
- #41517: Desktop/Dashboard chat worker loses selected profile and falls back to default
- #45506: Desktop voice mode uses default profile TTS config for non-default profiles
- #53441: Desktop audio endpoints ignore active profile config
- #64057: \[Bug\]: TTS voice selection ignores mismatched global vs profile config on desktop
- #66012: \[Bug\]: Hermes Desktop ignores per-profile tts/voice config; always uses global/default TTS provider

## Prior & competing PRs

- **#60180: fix(skills): profile-aware skills resolution in long-lived runtimes (#40677)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `tests/test_tui_gateway_server.py`, `tests/tools/test_skills_tool_profile_scope.py`, `tests/tui_gateway/test_slash_worker_profile_home.py`, `tools/skill_manager_tool.py`, `tools/skills_tool.py`, `tui_gateway/server.py`
  - fix hunks:
  - `tests/test_tui_gateway_server.py`

    ```diff
    @@ -1947,7 +1947,7 @@ def test_init_session_fires_reset_hook(monkeypatch):
         hooks = []
     
         class _FakeWorker:
    -        def __init__(self, key, model):
    +        def __init__(self, key, model, profile_home=None):
                 self.key = key
     
             def close(self):
    ```

    ```diff
    @@ -5453,7 +5453,7 @@ def test_session_create_close_race_does_not_orphan_worker(monkeypatch):
         unregistered_keys: list[str] = []
     
         class _FakeWorker:
    -        def __init__(self, key, model):
    +        def __init__(self, key, model, profile_home=None):
                 self.key = key
                 self._closed = False
     
    ```

    ```diff
    @@ -5574,7 +5574,7 @@ def test_session_create_no_race_keeps_worker_alive(monkeypatch):
         unregistered_keys: list[str] = []
     
         class _FakeWorker:
    -        def __init__(self, key, model):
    +        def __init__(self, key, model, profile_home=None):
                 self.key = key
     
             def close(self):
    ```

    ```diff
    @@ -5678,7 +5678,7 @@ def __init__(self):
     
     def test_session_create_continues_when_state_db_is_unavailable(monkeypatch):
         class _FakeWorker:
    -        def __init__(self, key, model):
    +        def __init__(self, key, model, profile_home=None):
                 self.key = key
     
             def close(self):
    ```

    ```diff
    @@ -5726,7 +5726,7 @@ def test_session_create_lazy_info_reports_desktop_contract(monkeypatch):
         date" on every launch even against a current backend."""
     
         class _FakeWorker:
    -        def __init__(self, key, model):
    +        def __init__(self, key, model, profile_home=None):
                 self.key = key
     
             def close(self):
    ```
  - `tests/tools/test_skills_tool_profile_scope.py`

    ```diff
    @@ -0,0 +1,105 @@
    +"""Regression tests for profile-scoped skills_tool path resolution."""
    +
    +import importlib
    +import json
    +from pathlib import Path
    +
    +
    +def _write_skill(root: Path, category: str, name: str, description: str) -> Path:
    +    skill_dir = root / "skills" / category / name
    +    skill_dir.mkdir(parents=True)
    +    (skill_dir / "SKILL.md").write_text(
    +        f"---\n"
    +        f"name: {name}\n"
    +        f"description: {description}\n"
    +        f"---\n\n"
    +        f"# {name}\n\n"
    +        f"Loaded from {description}.\n",
    +        encoding="utf-8",
    +    )
    +    return skill_dir
    +
    +
    +def _reload_skills_tool(import_home: Path, monkeypatch):
    +    monkeypatch.setenv("HERMES_HOME", str(import_home))
    +    import tools.skills_tool as skills_tool
    +
    +    return importlib.reload(skills_tool)
    +
    +
    +def test_skill_view_uses_live_profile_home_after_module_import(tmp_path, monkeypatch):
    +    """skill_view should not stay pinned to HERMES_HOME from import time."""
    +    default_home = tmp_path / "default-home"
    +    profile_home = tmp_path / "profiles" / "orchestrator"
    +    _write_skill(default_home, "autonomous-ai-agents", "default-only", "default home")
    +    profile_skill_dir = _write_skill(
    +        profile_home,
    +        "software-development",
    +        "kanban-orchestrator-operations",
    +        "orchestrator profile",
    +    )
    +
    +    skills_tool = _reload_skills_tool(default_home, monkeypatch)
    +    assert skills_tool.SKILLS_DIR == default_home / "skills"
    +
    +    monkeypatch.setenv("HERMES_HOME", str(profile_home))
    +
    +    result = json.loads(
    +        skills_tool.skill_view("kanban-orchestrator-operations", preprocess=False)
    +    )
    +
    +    assert result["success"] is True
    +    assert result["name"] == "kanban-orchestrator-operations"
    +    assert Path(result["skill_dir"]) == profile_skill_dir
    +    assert "orchestrator profile" in result["content"]
    +
    +
    +def test_skills_list_uses_live_profile_home_after_module_import(tmp_path, monkeypatch):
    +    """skills_list should list the active profile skills, not the import-time root."""
    +    default_home = tmp_path / "default-home"
    +    profile_home = tmp_path / "profiles" / "orchestrator"
    +    _write_skill(default_home, "autonomous-ai-agents", "default-only", "default home")
    +    _write_skill(
    +        profile_home,
    +        "software-development",
    +        "kanban-orchestrator-operations",
    +        "orchestrator profile",
    +    )
    +
    +    skills_tool = _reload_skills_tool(default_home, monkeypatch)
    +    monkeypatch.setenv("HERMES_HOME", str(profile_home))
    +
    +    result = json.loads(skills_tool.skills_list())
    +    names = {skill["name"] for skill in result["skills"]}
    +
    +    assert result["success"] is True
    +    assert "kanban-orchestrator-operations" in names
    +    assert "default-only" not in names
    +
    +
    +def test_explicit_skills_dir_monkeypatch_still_wins(tmp_path, monkeypatch):
    +    """Existing tests can still override tools.skills_tool.SKILLS_DIR directly."""
    +    default_home = tmp_path / "default-home"
    +    profile_home = tmp_path / "profiles" / "orchestrator"
    +    patched_root = tmp_path / "patched"
    +    patched_skill_dir = _write_skill(
    +        patched_root,
    +        "software-development",
    +        "patched-skill",
    +        "patched skills dir",
    +    )
    +    _write_skill(
    +        profile_home,
    +        "software-development",
    +        "profile-skill",
    +        "orchestrator profile",
    +    )
    +
    +    skills_tool = _reload_skills_tool(default_home, monkeypatch)
    +    monkeypatch.setenv("HERMES_HOME", str(profile_home))
    +    monkeypatch.setattr(skills_tool, "SKILLS_DIR", patched_root / "skills")
    +
    +    result = json.loads(skills_tool.skill_view("patched-skill", preprocess=False))
    +
    +    assert result["success"] is True
    +    assert Path(result["skill_dir"]) == patched_skill_dir
    ```
  - `tests/tui_gateway/test_slash_worker_profile_home.py`

    ```diff
    @@ -0,0 +1,124 @@
    +"""Tests for TUI gateway slash_worker profile_home propagation (#40677)."""
    +
    +import os
    +import subprocess
    +import sys
    +from unittest.mock import MagicMock, patch, call
    +
    +import pytest
    +
    +
    +def test_slash_worker_accepts_profile_home():
    +    """_SlashWorker.__init__ accepts profile_home parameter."""
    +    with patch.dict("sys.modules", {
    +        "hermes_constants": MagicMock(get_hermes_home=MagicMock(return_value="/tmp/hermes_test")),
    +    }):
    +        with patch("subprocess.Popen") as mock_popen:
    +            mock_popen.return_value.stdout = MagicMock()
    +            mock_popen.return_value.stderr = MagicMock()
    +            
    +            from tui_gateway.server import _SlashWorker
    +            
    +            # Test initialization with profile_home
    +            worker = _SlashWorker(
    +                session_key="test_key",
    +                model="test-model",
    +                profile_home="/home/luke/.hermes/profiles/work"
    +            )
    +            
    +            # Verify Popen was called
    +            assert mock_popen.called
    +            
    +            # Check that HERMES_HOME was set in the environment
    +            call_kwargs = mock_popen.call_args[1]
    +            assert "env" in call_kwargs
    +            assert call_kwargs["env"]["HERMES_HOME"] == "/home/luke/.hermes/profiles/work"
    +
    +
    +def test_slash_worker_without_profile_home():
    +    """_SlashWorker works without profile_home parameter (backward compatible)."""
    +    with patch.dict("sys.modules", {
    +        "hermes_constants": MagicMock(get_hermes_home=MagicMock(return_value="/tmp/hermes_test")),
    +    }):
    +        with patch("subprocess.Popen") as mock_popen:
    +            mock_popen.return_value.stdout = MagicMock()
    +            mock_popen.return_value.stderr = MagicMock()
    +            
    +            from tui_gateway.server import _SlashWorker
    +            
    +            # Test initialization without profile_home (backward compatible)
    +            worker = _SlashWorker(
    +                session_key="test_key",
    +                model="test-model"
    +            )
    +            
    +            # Verify Popen was called
    +            assert mock_popen.called
    +            
    +            # Check that HERMES_HOME was NOT overridden
    +            call_kwargs = mock_popen.call_args[1]
    +            assert "env" in call_kwargs
    +            # HERMES_HOME should be from parent env or undefined (inherited from os.environ)
    +            # The key is that it's not explicitly set when profile_home is None
    +            env = call_kwargs["env"]
    +            # Verify env is a copy of os.environ
    +            assert "PATH" in env
    +
    +
    +def test_slash_worker_with_none_profile_home():
    +    """_SlashWorker with explicit profile_home=None works."""
    +    with patch.dict("sys.modules", {
    +        "hermes_constants": MagicMock(get_hermes_home=MagicMock(return_value="/tmp/hermes_test")),
    +    }):
    +        with patch("subprocess.Popen") as mock_popen:
    +            mock_popen.return_value.stdout = MagicMock()
    +            mock_popen.return_value.stderr = MagicMock()
    +            
    +            from tui_gateway.server import _SlashWorker
    +            
    +            # Test initialization with explicit None
    +            worker = _SlashWorker(
    +                session_key="test_key",
    +                model="test-model",
    +                profile_home=None
    +            )
    +            
    +            # Verify Popen was called
    +            assert mock_popen.called
    +            
    +            # Check that HERMES_HOME was NOT set
    +            call_kwargs = mock_popen.call_args[1]
    +            env = call_kwargs["env"]
    +            # When profile_home is None, HERMES_HOME should come from parent env only
    +            if "HERMES_HOME" in env:
    +                # This is from os.environ at test time, not from our code
    +                pass
    +
    +
    +def test_slash_worker_inherits_argv_correctly():
    +    """_SlashWorker passes correct argv to Popen."""
    +    with patch.dict("sys.modules", {
    +        "hermes_constants": MagicMock(get_hermes_home=MagicMock(return_value="/tmp/hermes_test")),
    +    }):
    +        with patch("subprocess.Popen") as mock_popen:
    +            mock_popen.return_value.stdout = MagicMock()
    +            mock_popen.return_value.stderr = MagicMock()
    +            
    +            from tui_gateway.server import _SlashWorker
    +            
    +            # Test that argv is correct
    +            worker = _SlashWorker(
    +                session_key="my_session",
    +                model="gpt-4"
    +            )
    +            
    +            call_args = mock_popen.call_args[0][0]
    +            
    +            # Verify argv structure
    +            assert sys.executable in call_args
    +            assert "-m" in call_args
    +            assert "tui_gateway.slash_worker" in call_args
    +            assert "--session-key" in call_args
    +            assert "my_session" in call_args
    +            assert "--model" in call_args
    +            assert "gpt-4" in call_args
    ```
  - `tools/skill_manager_tool.py`

    ```diff
    @@ -150,6 +150,22 @@ def _security_scan_skill(skill_dir: Path) -> Optional[str]:
     # All skills live in ~/.hermes/skills/ (single source of truth)
     HERMES_HOME = get_hermes_home()
     SKILLS_DIR = HERMES_HOME / "skills"
    +_SKILLS_DIR_AT_IMPORT = SKILLS_DIR
    +
    +
    +def _skills_dir() -> Path:
    +    """Return the active profile's skills directory at call time.
    +
    +    Long-lived multi-profile runtimes (Dashboard/TUI/Desktop backend, cron,
    +    kanban workers) import this module once under the launch HERMES_HOME and
    +    later bind a different profile per session (#40677). Honor an explicitly
    +    patched module-level ``SKILLS_DIR`` (tests), otherwise resolve from the
    +    live profile-scoped HERMES_HOME on every call.
    +    """
    +    configured = Path(SKILLS_DIR)
    +    if configured != _SKILLS_DIR_AT_IMPORT:
    +        return configured
    +    return get_hermes_home() / "skills"
     
     MAX_NAME_LENGTH = 64
     MAX_DESCRIPTION_LENGTH = 1024
    ```

    ```diff
    @@ -174,7 +190,7 @@ def _containing_skills_root(skill_path: Path) -> Path:
                 return root
             except (ValueError, OSError):
                 continue
    -    return SKILLS_DIR
    +    return _skills_dir()
     
     
     def _is_path_redirect(path: Path) -> bool:
    ```

    ```diff
    @@ -562,8 +578,8 @@ def _validate_content_size(content: str, label: str = "SKILL.md") -> Optional[st
     def _resolve_skill_dir(name: str, category: str = None) -> Path:
         """Build the directory path for a new skill, optionally under a category."""
         if category:
    -        return SKILLS_DIR / category / name
    -    return SKILLS_DIR / name
    +        return _skills_dir() / category / name
    +    return _skills_dir() / name
     
     
     def _find_skill(name: str) -> Optional[Dict[str, Any]]:
    ```

    ```diff
    @@ -608,8 +624,9 @@ def _find_skill_in_other_profiles(name: str) -> List[Tuple[str, Path]]:
             return matches
     
         # Collect (profile_name, skills_dir) for every profile EXCEPT the
    -    # one whose SKILLS_DIR we already searched in _find_skill().
    -    active_dir = SKILLS_DIR.resolve() if SKILLS_DIR.exists() else SKILLS_DIR
    +    # one whose skills dir we already searched in _find_skill().
    +    _active = _skills_dir()
    +    active_dir = _active.resolve() if _active.exists() else _active
         candidates: List[Tuple[str, Path]] = []
     
         # Default profile (~/.hermes/skills) — only consider when active is non-default.
    ```

    ```diff
    @@ -828,7 +845,7 @@ def _create_skill(name: str, content: str, category: str = None) -> Dict[str, An
         result = {
             "success": True,
             "message": f"Skill '{name}' created.",
    -        "path": str(skill_dir.relative_to(SKILLS_DIR)),
    +        "path": str(skill_dir.relative_to(_skills_dir())),
             "skill_md": str(skill_md),
             "_change": {"description": _desc},
         }
    ```
  - `tools/skills_tool.py`

    ```diff
    @@ -92,6 +92,22 @@
     # skills all coexist here without polluting the git repo.
     HERMES_HOME = get_hermes_home()
     SKILLS_DIR = HERMES_HOME / "skills"
    +_SKILLS_DIR_AT_IMPORT = SKILLS_DIR
    +
    +
    +def _skills_dir() -> Path:
    +    """Return the active profile's skills directory at call time.
    +
    +    Some long-lived runtimes import this module before the active profile has
    +    set HERMES_HOME. Keep the legacy SKILLS_DIR module attribute for tests and
    +    external patchers, but when it has not been patched, resolve from the live
    +    profile-scoped HERMES_HOME on every call.
    +    """
    +    configured = Path(SKILLS_DIR)
    +    if configured != _SKILLS_DIR_AT_IMPORT:
    +        return configured
    +    return get_hermes_home() / "skills"
    +
     
     # Anthropic-recommended limits for progressive disclosure efficiency
     MAX_NAME_LENGTH = 64
    ```

    ```diff
    @@ -501,9 +517,9 @@ def _get_category_from_path(skill_path: Path) -> Optional[str]:
         For paths like: ~/.hermes/skills/mlops/axolotl/SKILL.md -> "mlops"
         Also works for external skill dirs configured via skills.external_dirs.
         """
    -    # Try the module-level SKILLS_DIR first (respects monkeypatching in tests),
    +    # Try the active profile skills dir first (respects monkeypatching in tests),
         # then fall back to external dirs from config.
    -    dirs_to_check = [SKILLS_DIR]
    +    dirs_to_check = [_skills_dir()]
         try:
             from agent.skill_utils import get_external_skills_dirs
             dirs_to_check.extend(get_external_skills_dirs())
    ```

    ```diff
    @@ -622,8 +638,9 @@ def _find_all_skills(*, skip_disabled: bool = False) -> List[Dict[str, Any]]:
     
         # Scan local dir first, then external dirs (local takes precedence)
         dirs_to_scan = []
    -    if SKILLS_DIR.exists():
    -        dirs_to_scan.append(SKILLS_DIR)
    +    active_skills_dir = _skills_dir()
    +    if active_skills_dir.exists():
    +        dirs_to_scan.append(active_skills_dir)
         dirs_to_scan.extend(get_external_skills_dirs())
     
         for scan_dir in dirs_to_scan:
    ```

    ```diff
    @@ -701,8 +718,9 @@ def skills_list(category: str = None, task_id: str = None) -> str:
             JSON string with minimal skill info: name, description, category
         """
         try:
    -        if not SKILLS_DIR.exists():
    -            SKILLS_DIR.mkdir(parents=True, exist_ok=True)
    +        active_skills_dir = _skills_dir()
    +        if not active_skills_dir.exists():
    +            active_skills_dir.mkdir(parents=True, exist_ok=True)
                 return json.dumps(
                     {
                         "success": True,
    ```

    ```diff
    @@ -984,8 +1002,9 @@ def skill_view(
     
             # Build list of all skill directories to search
             all_dirs = []
    -        if SKILLS_DIR.exists():
    -            all_dirs.append(SKILLS_DIR)
    +        active_skills_dir = _skills_dir()
    +        if active_skills_dir.exists():
    +            all_dirs.append(active_skills_dir)
             all_dirs.extend(get_external_skills_dirs())
     
             if not all_dirs:
    ```

    ```diff
    @@ -1135,7 +1154,7 @@ def _record(sd: Optional[Path], smd: Path) -> None:
             # Security: warn if skill is loaded from outside trusted directories
             # (local skills dir + configured external_dirs are all trusted)
             _outside_skills_dir = True
    -        _trusted_dirs = [SKILLS_DIR.resolve()]
    +        _trusted_dirs = [active_skills_dir.resolve()]
             try:
                 _trusted_dirs.extend(d.resolve() for d in all_dirs[1:])
             except Exception:
    ```

    ```diff
    @@ -1375,7 +1394,7 @@ def _record(sd: Optional[Path], smd: Path) -> None:
                 linked_files["scripts"] = script_files
     
             try:
    -            rel_path = str(skill_md.relative_to(SKILLS_DIR))
    +            rel_path = str(skill_md.relative_to(active_skills_dir))
             except ValueError:
                 # External skill — use path relative to the skill's own parent dir
                 rel_path = str(skill_md.relative_to(skill_md.parent.parent)) if skill_md.parent.parent else skill_md.name
    ```
  - `tui_gateway/server.py`

    ```diff
    @@ -275,7 +275,7 @@ def close(self) -> None:
     class _SlashWorker:
         """Persistent HermesCLI subprocess for slash commands."""
     
    -    def __init__(self, session_key: str, model: str):
    +    def __init__(self, session_key: str, model: str, profile_home: str | None = None):
             self._lock = threading.Lock()
             self._seq = 0
             self.stderr_tail: list[str] = []
    ```

    ```diff
    @@ -294,6 +294,15 @@ def __init__(self, session_key: str, model: str):
             self._closed = False
             from hermes_cli._subprocess_compat import windows_hide_flags
     
    +        # slash_worker runs the Hermes agent → needs provider credentials.
    +        # Tier-1 secrets (gateway/GitHub/infra) are still stripped (#29157).
    +        env = hermes_subprocess_env(inherit_credentials=True)
    +        if profile_home:
    +            # Global-remote / multi-profile sessions: the worker must resolve
    +            # config/skills/state against the session's profile home, not the
    +            # gateway's launch HERMES_HOME (#40677).
    +            env["HERMES_HOME"] = str(profile_home)
    +
             # start_new_session=True detaches the slash worker into its own
             # process group / session. Without this, the worker inherits the
             # gateway's pgid (= TUI parent PID). When mcp_tool's
    ```

    ```diff
    @@ -310,9 +319,7 @@ def __init__(self, session_key: str, model: str):
                 text=True,
                 bufsize=1,
                 cwd=os.getcwd(),
    -            # slash_worker runs the Hermes agent → needs provider credentials.
    -            # Tier-1 secrets (gateway/GitHub/infra) are still stripped (#29157).
    -            env=hermes_subprocess_env(inherit_credentials=[redacted:secret-assignment]
    +            env=env,
                 creationflags=windows_hide_flags(),
                 start_new_session=True,
             )
    ```

    ```diff
    @@ -1299,7 +1306,11 @@ def _build() -> None:
                 current["config_model_seen"] = _config_model_target()
     
                 try:
    -                worker = _SlashWorker(key, getattr(agent, "model", _resolve_model()))
    +                worker = _SlashWorker(
    +                    key,
    +                    getattr(agent, "model", _resolve_model()),
    +                    profile_home=current.get("profile_home"),
    +                )
                     _attach_worker(sid, current, worker)
                 except Exception:
                     pass
    ```

    ```diff
    @@ -2628,6 +2639,7 @@ def _restart_slash_worker(sid: str, session: dict):
             new_worker = _SlashWorker(
                 session["session_key"],
                 getattr(session.get("agent"), "model", _resolve_model()),
    +            profile_home=session.get("profile_home"),
             )
         except Exception:
             session["slash_worker"] = None
    ```

    ```diff
    @@ -4514,7 +4526,11 @@ def _init_session(
             _attach_worker(
                 sid,
                 _sessions[sid],
    -            _SlashWorker(key, getattr(agent, "model", _resolve_model())),
    +            _SlashWorker(
    +                key,
    +                getattr(agent, "model", _resolve_model()),
    +                profile_home=_sessions[sid].get("profile_home"),
    +            ),
             )
         except Exception:
             # Defer hard-failure to slash.exec; chat still works without slash worker.
    ```

    ```diff
    @@ -12710,6 +12726,7 @@ def _(rid, params: dict) -> dict:
                 worker = _SlashWorker(
                     session["session_key"],
                     getattr(session.get("agent"), "model", _resolve_model()),
    +                profile_home=session.get("profile_home"),
                 )
                 _attach_worker(params.get("session_id", ""), session, worker)
             except Exception as e:
    ```
- **#60257: fix(skills): invalidate slash-command cache on profile change in long-lived runtimes**. closed without merge · fix tier `suggested`
  - maintainer direction: category `superseded_by_pr`; implication `superseded`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#73521: fix(desktop): voice profile scoping, remote media playback, stop-button state, read-aloud content**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `apps/desktop/src/app/chat/composer/hooks/use-voice-conversation.ts`, `apps/desktop/src/components/assistant-ui/markdown-text.media.test.tsx`, `apps/desktop/src/components/assistant-ui/markdown-text.tsx`, `apps/desktop/src/hermes-profile-scope.test.ts`, `apps/desktop/src/hermes.test.ts`, `apps/desktop/src/hermes.ts`, `apps/desktop/src/lib/media.remote.test.ts`, `apps/desktop/src/lib/media.ts`, `apps/desktop/src/lib/speech-text.test.ts`, `apps/desktop/src/lib/speech-text.ts`, `apps/desktop/src/lib/voice-playback.ts`, `contributors/emails/[redacted:email]`, `contributors/emails/[redacted:email]`, `contributors/emails/[redacted:email]`, `hermes_cli/web_server.py`, `tests/hermes_cli/test_web_server_profile_unification.py`
- **#40959: fix(tui): pass profile_home to slash_worker subprocess (#40677)**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#60180`, `#36847`, `#56689`, `#40677`
- **#41526: fix(tui_gateway): pass profile to slash worker subprocess**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `4a99571d54e0e0ca0dddd7918ba32446ac115362`, `#40677`
- **#45522: fix(desktop): route TTS through active profile**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#53470`
- **#53470: fix(desktop): scope audio endpoints to the active profile**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `097f0d0167`, `#73521`
- **#53472: fix(desktop): scope audio helpers to active profile**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `097f0d0167`, `#73521`, `#53470`, `#69491`, `#73521`
- **#53488: fix: scope desktop audio requests to profile**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `097f0d0167`, `#73521`, `#53470`, `#69491`, `#73521`

## Fix status

- Merged to main in PR #60180 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-07-28`.
- Subsystem: `apps/desktop`
