fix(skills): resolve project roots from session cwd
This commit is contained in:
@@ -419,20 +419,20 @@ _PROJECT_ROOT_MAX_DEPTH = 64 # walk-up bound for pathological cwds
|
||||
|
||||
def find_project_root(start: Optional[Path] = None) -> Optional[Path]:
|
||||
"""Nearest ancestor containing ``.git`` (dir or worktree file), or None.
|
||||
Without *start*, the surface's ``TERMINAL_CWD`` wins over process cwd so
|
||||
cron/API surfaces inherit an interactive trust decision by project identity.
|
||||
|
||||
When *start* is not given, the surface's working directory wins over the process cwd: ``TERMINAL_CWD``
|
||||
is the same per-surface workdir the terminal tool and cron jobs use (a cron job sets it from its per-job
|
||||
``workdir`` without chdir'ing the scheduler process). This is what lets non-interactive surfaces inherit
|
||||
a prior interactive trust decision by project identity — and a surface with no workdir in a trusted repo
|
||||
simply resolves no project and loads nothing (#48975).
|
||||
Without *start*, the surface's effective working directory wins over the process cwd — the same
|
||||
ladder every other cwd consumer reads (``resolve_agent_cwd``: session-bound cwd, then the scope's
|
||||
``TERMINAL_CWD``, then the process cwd). The session cwd comes first because a multi-session host
|
||||
(TUI/desktop gateway) pins each session's workspace there while its terminal scope resolves a
|
||||
placeholder ``terminal.cwd`` to ``$HOME``; reading only the scope made every project skill invisible
|
||||
on those surfaces (#114359). ``TERMINAL_CWD`` is the per-surface workdir the terminal tool and cron
|
||||
jobs use (a cron job sets it from its per-job ``workdir`` without chdir'ing the scheduler process),
|
||||
which lets non-interactive surfaces inherit a prior interactive trust decision by project identity —
|
||||
and a surface with no workdir in a trusted repo simply resolves no project and loads nothing (#48975).
|
||||
"""
|
||||
try:
|
||||
if start is None:
|
||||
from agent.runtime_cwd import scope_terminal_cwd
|
||||
env_cwd = scope_terminal_cwd()
|
||||
start = Path(env_cwd) if env_cwd else Path.cwd()
|
||||
from agent.runtime_cwd import resolve_agent_cwd
|
||||
start = resolve_agent_cwd()
|
||||
cur = Path(start).resolve()
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
@@ -139,6 +139,25 @@ class TestNonInteractiveInheritance:
|
||||
assert su.find_project_root() == project_env["repo"].resolve()
|
||||
assert su.get_project_skills_dirs() != []
|
||||
|
||||
def test_session_cwd_beats_backend_launch_cwd(
|
||||
self, project_env, monkeypatch, tmp_path
|
||||
):
|
||||
"""Desktop project skills follow the active session, not launch cwd."""
|
||||
from gateway.session_context import clear_session_vars, set_session_vars
|
||||
|
||||
launcher = tmp_path / "desktop-launcher"
|
||||
launcher.mkdir()
|
||||
monkeypatch.chdir(launcher)
|
||||
monkeypatch.setenv("TERMINAL_CWD", str(launcher))
|
||||
_trust(project_env["config"], project_env["repo"])
|
||||
|
||||
tokens = set_session_vars(cwd=str(project_env["repo"]))
|
||||
try:
|
||||
assert su.find_project_root() == project_env["repo"].resolve()
|
||||
assert su.get_project_skills_dirs() != []
|
||||
finally:
|
||||
clear_session_vars(tokens)
|
||||
|
||||
def test_no_workdir_no_trust_inheritance(self, project_env, monkeypatch, tmp_path):
|
||||
# A surface running outside any repo (API server from home-like dir)
|
||||
# resolves no project even when OTHER repos are trusted.
|
||||
|
||||
Reference in New Issue
Block a user