# NousResearch/hermes-agent · Profile switch CWD and terminal.cwd priority fixes

## Open now

- 0 issues, 3 PRs open on GitHub.
- PR #56869: fix(tui_gateway): prefer profile terminal.cwd over stale client-sent cwd on profile switch
- PR #61653: fix: profile terminal.cwd overrides desktop app-global workspace (#52589)
- PR #63976: fix(desktop): clear project scope on profile switch

A cluster of bugs and fixes around the Desktop app failing to apply the correct working directory when switching profiles, specifically that terminal.cwd from the target profile was being ignored in favor of stale workspace or previous-profile paths.

## TL;DR

- 9 member(s): 0 open issue(s), 3 closed issue(s), 3 open PR(s), 2 merged PR(s), 1 PR(s) closed without merging.
- A fix was merged to main in PR #122310. Verify it addresses your case.
- **Recommended:** read PR #122310 (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-40892,pr-122310`. 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.20`: 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

- #40334: Desktop: profile switch ignores terminal.cwd, uses wrong profile's working directory
- #52589: Desktop app-global workspace overrides profile-specific terminal.cwd
- #54990: \[Bug\]: Profile switch in Desktop app leaves shell CWD stuck in previous profile's workspace

## Prior & competing PRs

- **#40892: fix(tui_gateway): honor target profile's terminal.cwd on desktop profile switch**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `tests/test_tui_gateway_server.py`, `tui_gateway/server.py`
  - fix hunks:
  - `tests/test_tui_gateway_server.py`

    ```diff
    @@ -60,6 +60,64 @@ def test_session_context_explicit_cwd_for_ephemeral_task(monkeypatch, tmp_path):
             server._clear_session_context(tokens)
     
     
    +def _write_profile_cfg(home: Path, cwd: str | None) -> Path:
    +    import yaml
    +
    +    home.mkdir(parents=True, exist_ok=True)
    +    cfg = {"terminal": {"cwd": cwd}} if cwd is not None else {}
    +    (home / "config.yaml").write_text(yaml.safe_dump(cfg), encoding="utf-8")
    +    return home
    +
    +
    +def test_profile_configured_cwd_reads_target_profile(tmp_path):
    +    """A profile's own terminal.cwd is read from its config.yaml."""
    +    project = tmp_path / "proj"
    +    project.mkdir()
    +    home = _write_profile_cfg(tmp_path / "home", str(project))
    +    assert server._profile_configured_cwd(home) == str(project)
    +
    +
    +def test_profile_configured_cwd_skips_placeholders_and_missing(tmp_path):
    +    """Placeholder values, missing config, and bad paths fall through to None."""
    +    assert server._profile_configured_cwd(None) is None
    +    assert server._profile_configured_cwd(tmp_path / "nope") is None
    +    for placeholder in (".", "auto", "cwd", ""):
    +        home = _write_profile_cfg(tmp_path / placeholder.strip("."), placeholder)
    +        assert server._profile_configured_cwd(home) is None
    +    home = _write_profile_cfg(tmp_path / "ghost", str(tmp_path / "does-not-exist"))
    +    assert server._profile_configured_cwd(home) is None
    +
    +
    +def test_completion_cwd_prefers_profile_over_stale_env(monkeypatch, tmp_path):
    +    """Issue #40334: a new session bound to another profile must use THAT
    +    profile's terminal.cwd, not the launch profile's stale TERMINAL_CWD."""
    +    profile_b = tmp_path / "ef-design"
    +    profile_b.mkdir()
    +    home = _write_profile_cfg(tmp_path / "home-b", str(profile_b))
    +    stale = tmp_path / "mahjong"
    +    stale.mkdir()
    +
    +    monkeypatch.setenv("TERMINAL_CWD", str(stale))
    +    monkeypatch.setattr(server, "_profile_home", lambda name: home if name else None)
    +
    +    assert server._completion_cwd({"profile": "ef-design"}) == str(profile_b)
    +    # No profile → unchanged fallback to the launch env var.
    +    assert server._completion_cwd({}) == str(stale)
    +
    +
    +def test_completion_cwd_explicit_cwd_wins_over_profile(monkeypatch, tmp_path):
    +    """An explicit client-provided cwd still beats the profile config."""
    +    explicit = tmp_path / "explicit"
    +    explicit.mkdir()
    +    profile_b = tmp_path / "configured"
    +    profile_b.mkdir()
    +    home = _write_profile_cfg(tmp_path / "home-c", str(profile_b))
    +
    +    monkeypatch.setattr(server, "_profile_home", lambda name: home if name else None)
    +    result = server._completion_cwd({"cwd": str(explicit), "profile": "ef-design"})
    +    assert result == str(explicit)
    +
    +
     def test_terminal_task_cwd_local_backend_uses_session_cwd(monkeypatch, tmp_path):
         """A local terminal backend must keep host-validated session cwd behaviour."""
         project = tmp_path / "project"
    ```
  - `tui_gateway/server.py`

    ```diff
    @@ -488,6 +488,40 @@ def _profile_home(profile: str | None) -> Path | None:
         return home if (home / "state.db").exists() or home.exists() else None
     
     
    +# Placeholder ``terminal.cwd`` values that don't name a real directory — the
    +# gateway resolves these to the home dir at runtime, so they must NOT be treated
    +# as an explicit workspace (mirrors gateway/run.py's config bridge).
    +_CWD_PLACEHOLDERS = {".", "auto", "cwd"}
    +
    +
    +def _profile_configured_cwd(profile_home: Path | None) -> str | None:
    +    """Resolve a non-launch profile's ``terminal.cwd`` from its own config.yaml.
    +
    +    The desktop's app-global remote mode serves every profile from one backend,
    +    so the process-global ``TERMINAL_CWD`` belongs to the *launch* profile. A new
    +    session bound to another profile must take its workspace from THAT profile's
    +    config, not the stale env var (issue #40334). Returns an absolute, existing
    +    directory, or None for placeholders / missing / invalid paths.
    +    """
    +    if profile_home is None:
    +        return None
    +    try:
    +        import yaml
    +
    +        p = Path(profile_home) / "config.yaml"
    +        if not p.exists():
    +            return None
    +        with open(p, encoding="utf-8") as f:
    +            data = yaml.safe_load(f) or {}
    +        raw = str((data.get("terminal") or {}).get("cwd") or "").strip()
    +        if not raw or raw in _CWD_PLACEHOLDERS:
    +            return None
    +        resolved = os.path.abspath(os.path.expanduser(raw))
    +        return resolved if os.path.isdir(resolved) else None
    +    except Exception:
    +        return None
    +
    +
     def write_json(obj: dict) -> bool:
         """Emit one JSON frame. Routes via the most-specific transport available.
     
    ```

    ```diff
    @@ -798,9 +832,13 @@ def _normalize_completion_path(path_part: str) -> str:
     
     
     def _completion_cwd(params: dict | None = None) -> str:
    +    params = params or {}
         raw = (
    -        (params or {}).get("cwd")
    -        or _sessions.get((params or {}).get("session_id") or "", {}).get("cwd")
    +        params.get("cwd")
    +        or _sessions.get(params.get("session_id") or "", {}).get("cwd")
    +        # A session bound to another profile resolves its workspace from THAT
    +        # profile's config before falling back to the launch profile's env var.
    +        or _profile_configured_cwd(_profile_home(params.get("profile")))
             or os.environ.get("TERMINAL_CWD")
             or os.getcwd()
         )
    ```
- **#122310: fix(desktop): leave the entered project when switching profile or backend**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `apps/desktop/src/app/chat/sidebar/chat-sidebar.integration.test.tsx`, `apps/desktop/src/app/chat/sidebar/index.tsx`, `apps/desktop/src/app/session/hooks/default-new-session.test.tsx`, `apps/desktop/src/app/session/hooks/use-session-actions.test.tsx`, `apps/desktop/src/app/session/hooks/use-session-actions/index.ts`, `apps/desktop/src/app/session/new-session-route.ts`, `apps/desktop/src/app/session/workspace-session-target.test.ts`, `apps/desktop/src/store/coding-status.ts`, `apps/desktop/src/store/gateway-switch.test.ts`, `apps/desktop/src/store/gateway-switch.ts`, `apps/desktop/src/store/profile.test.ts`, `apps/desktop/src/store/profile.ts`, `apps/desktop/src/store/project-scope.ts`, `apps/desktop/src/store/projects.test.ts`, `apps/desktop/src/store/projects.ts`
- **#56869: fix(tui_gateway): prefer profile terminal.cwd over stale client-sent cwd on profile switch**. open
  - maintainer direction: category `uncategorized`; implication `needs_llm`; refs `93340fa3c`, `f3af7930c`
  - files: `tests/test_tui_gateway_server.py`, `tui_gateway/server.py`
- **#61653: fix: profile terminal.cwd overrides desktop app-global workspace (#52589)**. open
  - maintainer direction: category `uncategorized`; implication `needs_llm`
  - files: `apps/desktop/src/app/session/hooks/use-cwd-actions.ts`, `apps/desktop/src/app/session/hooks/use-session-actions.test.tsx`, `apps/desktop/src/app/session/hooks/use-session-actions/index.ts`, `apps/desktop/src/store/session.ts`, `tests/test_tui_gateway_server.py`, `tui_gateway/server.py`
- **#63976: fix(desktop): clear project scope on profile switch**. open
  - maintainer direction: category `partial_fix`; implication `needs_llm`; refs `65c2b249345e`, `#40892`, `#43082`, `#56869`, `#61653`, `#63976`, `#40334`, `#54990`, `#374151`, `#52589`
  - files: `apps/desktop/src/app/contrib/wiring.tsx`, `apps/desktop/src/store/profile.test.ts`, `apps/desktop/src/store/profile.ts`, `apps/desktop/src/store/project-scope.ts`, `apps/desktop/src/store/projects.ts`
- **#122898: fix: profile terminal.cwd beats the desktops inherited workspace cwd**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `a9972dc3f9`

## Fix status

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