From 182409c2ac5e6bd9229bc862106e681b83cac7e5 Mon Sep 17 00:00:00 2001 From: ciregenz Date: Tue, 18 Aug 2026 19:04:56 -0700 Subject: [PATCH] [eric] agents: every git exec rides a non-prompting availability check; the CLT shim's install dialog fired at session start on clean Macs (ENG-344) --- .../manager/prompt/repo_staleness_note.py | 4 +++ .../agents/manager/session/workspace_git.py | 34 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/backend/apps/agents/manager/prompt/repo_staleness_note.py b/backend/apps/agents/manager/prompt/repo_staleness_note.py index a57d2859..3e0f827d 100644 --- a/backend/apps/agents/manager/prompt/repo_staleness_note.py +++ b/backend/apps/agents/manager/prompt/repo_staleness_note.py @@ -17,6 +17,8 @@ from typing import List, Optional from typeguard import typechecked +from backend.apps.agents.manager.session.workspace_git import git_available + P_TIMEOUT_S = 2.0 # Below this, a checkout is normal working drift and a note would just be noise. MIN_BEHIND_TO_WARN = 20 @@ -25,6 +27,8 @@ MIN_BEHIND_TO_WARN = 20 @typechecked def p_git(cwd: str, args: List[str]) -> Optional[str]: """Run a read-only git command, or None if anything at all goes wrong.""" + if not git_available(): + return None try: out = subprocess.run( ["git", *args], cwd=cwd, capture_output=True, text=True, timeout=P_TIMEOUT_S, diff --git a/backend/apps/agents/manager/session/workspace_git.py b/backend/apps/agents/manager/session/workspace_git.py index 51b77590..aebe8e2f 100644 --- a/backend/apps/agents/manager/session/workspace_git.py +++ b/backend/apps/agents/manager/session/workspace_git.py @@ -6,6 +6,36 @@ from typeguard import typechecked logger = logging.getLogger(__name__) +p_git_ok: Optional[bool] = None + + +@typechecked +def git_available() -> bool: + """True when running `git` won't pop an OS installer dialog (ENG-344). + + On a Mac without Xcode Command Line Tools, /usr/bin/git is a shim whose EXECUTION opens the + modal "install the developer tools?" prompt, so which("git") alone is a trap there: presence + must be proven by `xcode-select -p` (never prompts) or by a git that isn't the shim.""" + global p_git_ok + if p_git_ok is not None: + return p_git_ok + import shutil + import subprocess as sp + import sys + found = shutil.which("git") + if not found: + p_git_ok = False + elif sys.platform != "darwin" or found != "/usr/bin/git": + p_git_ok = True + else: + try: + p_git_ok = sp.run(["/usr/bin/xcode-select", "-p"], capture_output=True, timeout=5).returncode == 0 + except Exception: + p_git_ok = False + if not p_git_ok: + logger.info("[agent-cwd] git unavailable (or would prompt to install); skipping all git integration") + return p_git_ok + @typechecked def ensure_cwd_git_repo(cwd: str, home: Optional[str] = None) -> None: @@ -22,6 +52,8 @@ def ensure_cwd_git_repo(cwd: str, home: Optional[str] = None) -> None: Safe to call on every request, does nothing if cwd is already a valid repo (real project, previous init, or inside a parent repo). """ + if not git_available(): + return try: home = home or os.path.expanduser("~") cwd_abs = os.path.abspath(cwd) @@ -92,6 +124,8 @@ def detect_git_identity(cwd: str) -> Tuple[Optional[str], Optional[str]]: subprocess failure. Credentials in the URL are stripped so a `https://user:token@host/...` remote becomes `https://host/...`. """ + if not git_available(): + return (None, None) if not cwd or not os.path.isdir(cwd): return (None, None) try: