Profile switch CWD and terminal.cwd priority fixes
Cluster 40334 · apps/desktop. Data as of
3 open items. Fix merged in #122310 on 25 Sep 2026. Check it covers your case.
The short answer
- Known?
- Yes. 9 related items grouped here, 3 issues already closed.
- Fix event
- A fix was merged to main in PR #122310. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
partially resolved, deciding PRs #40892, #122310. That is separate from the fix event. - Still open
- 0 issues and 3 PRs.
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.
Open issues0
Open PRs3
Fixes on record
-
#40892 fix(tui_gateway): honor target profile's terminal.cwd on desktop profile switch
files:
tests/test_tui_gateway_server.py,tui_gateway/server.pyShow the diff
-
tests/test_tui_gateway_server.py@@ -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@@ -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. @@ -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
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
1 PR closed without merging
From the close comments: done elsewhere 1
-
#122898 fix: profile terminal.cwd beats the desktops inherited workspace cwd · done elsewhere →
a9972dc
Closed issues · 3
How this cluster was grouped
-
Link evidence (
partly structural links): 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 item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the Lab