From 4a02ba4462e40f9c4754119bcc5f5931583ad088 Mon Sep 17 00:00:00 2001 From: Mark Kuckian Date: Mon, 4 May 2026 18:01:11 +0300 Subject: [PATCH 1/5] fix: skip ssh multiplex on windows where ControlMaster is unreliable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Microsoft OpenSSH port that ships with Windows 10/11 has flaky ControlMaster support: the master commonly drops mid-session with "Connection reset by peer" and the stale socket file then blocks subsequent multiplex attempts. Reported user output: mux_client_request_session: read from master failed: Connection reset by peer ControlSocket C:\Users\jorma/.ssh/cm\ already exists, disabling multiplexing Skip the ControlMaster/ControlPath/ControlPersist `-o` flags entirely when running on Windows. Keepalives are still added (those work fine), parallel_load + the fetch semaphore continue to bound concurrency, and each fetch falls back to its own simple connection — auth happens N times instead of once, but correctness wins over the perf optimisation. Windows users who still want multiplexing can configure it in ~/.ssh/config explicitly (e.g. via WSL or a Cygwin ssh); we'll detect their config via `ssh -G` and respect it. Co-Authored-By: Claude Opus 4.7 (1M context) --- mama/utils/ssh_multiplex.py | 17 +++++++++++++++-- tests/test_ssh_multiplex/test_ssh_multiplex.py | 17 +++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/mama/utils/ssh_multiplex.py b/mama/utils/ssh_multiplex.py index 66e5024..bd5eb45 100644 --- a/mama/utils/ssh_multiplex.py +++ b/mama/utils/ssh_multiplex.py @@ -137,15 +137,28 @@ def is_multiplex_configured(probe: dict[str, str]) -> bool: return cm not in ('no', 'false', '') and cp not in ('none', '', 'no') +def _is_windows() -> bool: + return os.name == 'nt' + + def options_to_add(probe: dict[str, str]) -> tuple[list[str], bool]: """ Return (-o args, we_own_master). `we_own_master` is True when we are the one configuring multiplex (and therefore responsible for pre-warming and - cleaning it up). False if the user already has multiplex configured. + cleaning it up). False if the user already has multiplex configured, or + if multiplex is unsupported on this platform. """ opts: list[str] = [] we_own_master = False - if not is_multiplex_configured(probe): + # Windows OpenSSH (Microsoft port shipping in Win10/11) has unreliable + # ControlMaster support: the master commonly drops mid-session with + # "Connection reset by peer" and the stale socket file then blocks new + # multiplex attempts. Skip multiplex entirely on Windows; keepalives + # still work fine and parallel_load + the fetch semaphore continue to + # bound git concurrency. Users who want multiplex on Windows should + # configure it explicitly in ~/.ssh/config (e.g. via WSL or a Cygwin + # ssh) — we'll detect their config via `ssh -G` and respect it. + if not _is_windows() and not is_multiplex_configured(probe): we_own_master = True os.makedirs(_OUR_CONTROL_DIR, mode=0o700, exist_ok=True) opts += [ diff --git a/tests/test_ssh_multiplex/test_ssh_multiplex.py b/tests/test_ssh_multiplex/test_ssh_multiplex.py index 7061d44..b4b846d 100644 --- a/tests/test_ssh_multiplex/test_ssh_multiplex.py +++ b/tests/test_ssh_multiplex/test_ssh_multiplex.py @@ -144,6 +144,23 @@ def test_user_has_multiplex_only(self): assert not any(o.startswith('-oControlPath=') for o in opts) assert any(o.startswith('-oServerAliveInterval=') for o in opts) + def test_windows_skips_multiplex_keeps_keepalives(self, monkeypatch, tmp_path): + # Microsoft OpenSSH on Windows has unreliable ControlMaster — the + # master drops mid-session and leaves the socket file behind. We + # disable multiplex on Windows entirely; keepalives are still useful. + monkeypatch.setattr(sm, '_is_windows', lambda: True) + monkeypatch.setattr(sm, '_OUR_CONTROL_DIR', str(tmp_path / 'cm')) + monkeypatch.setattr(sm, '_OUR_CONTROL_PATH', str(tmp_path / 'cm' / '%C')) + probe = {'controlmaster': 'no', 'controlpath': 'none', + 'serveraliveinterval': '0'} + opts, we_own = sm.options_to_add(probe) + assert we_own is False + assert not any(o.startswith('-oControlMaster=') for o in opts) + assert not any(o.startswith('-oControlPath=') for o in opts) + assert not any(o.startswith('-oControlPersist=') for o in opts) + assert any(o.startswith('-oServerAliveInterval=') for o in opts) + assert any(o.startswith('-oServerAliveCountMax=') for o in opts) + class TestProbeSshConfig: def test_parses_keys(self): From f098b2d800e252bfb644840fbc9372f22e784f9f Mon Sep 17 00:00:00 2001 From: Mark Kuckian Date: Tue, 5 May 2026 10:22:46 +0300 Subject: [PATCH 2/5] ssh_multiplex: use existing System.windows static helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per review feedback on #18 — mama.utils.system.System already provides static platform checks set from sys.platform at import time, so use System.windows instead of a local _is_windows(). Co-Authored-By: Claude Opus 4.7 (1M context) --- mama/utils/ssh_multiplex.py | 8 +++----- tests/test_ssh_multiplex/test_ssh_multiplex.py | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/mama/utils/ssh_multiplex.py b/mama/utils/ssh_multiplex.py index bd5eb45..4a82335 100644 --- a/mama/utils/ssh_multiplex.py +++ b/mama/utils/ssh_multiplex.py @@ -34,6 +34,8 @@ import threading from urllib.parse import urlparse +from .system import System + DEFAULT_MAX_CONCURRENT_FETCHES = 20 @@ -137,10 +139,6 @@ def is_multiplex_configured(probe: dict[str, str]) -> bool: return cm not in ('no', 'false', '') and cp not in ('none', '', 'no') -def _is_windows() -> bool: - return os.name == 'nt' - - def options_to_add(probe: dict[str, str]) -> tuple[list[str], bool]: """ Return (-o args, we_own_master). `we_own_master` is True when we are the @@ -158,7 +156,7 @@ def options_to_add(probe: dict[str, str]) -> tuple[list[str], bool]: # bound git concurrency. Users who want multiplex on Windows should # configure it explicitly in ~/.ssh/config (e.g. via WSL or a Cygwin # ssh) — we'll detect their config via `ssh -G` and respect it. - if not _is_windows() and not is_multiplex_configured(probe): + if not System.windows and not is_multiplex_configured(probe): we_own_master = True os.makedirs(_OUR_CONTROL_DIR, mode=0o700, exist_ok=True) opts += [ diff --git a/tests/test_ssh_multiplex/test_ssh_multiplex.py b/tests/test_ssh_multiplex/test_ssh_multiplex.py index b4b846d..0d97518 100644 --- a/tests/test_ssh_multiplex/test_ssh_multiplex.py +++ b/tests/test_ssh_multiplex/test_ssh_multiplex.py @@ -148,7 +148,7 @@ def test_windows_skips_multiplex_keeps_keepalives(self, monkeypatch, tmp_path): # Microsoft OpenSSH on Windows has unreliable ControlMaster — the # master drops mid-session and leaves the socket file behind. We # disable multiplex on Windows entirely; keepalives are still useful. - monkeypatch.setattr(sm, '_is_windows', lambda: True) + monkeypatch.setattr(sm.System, 'windows', True) monkeypatch.setattr(sm, '_OUR_CONTROL_DIR', str(tmp_path / 'cm')) monkeypatch.setattr(sm, '_OUR_CONTROL_PATH', str(tmp_path / 'cm' / '%C')) probe = {'controlmaster': 'no', 'controlpath': 'none', From 4bb6c77946b75d97c2c9ec6273f243448b914dab Mon Sep 17 00:00:00 2001 From: Mark Kuckian Date: Tue, 5 May 2026 10:50:00 +0300 Subject: [PATCH 3/5] ssh_multiplex: detect buggy ssh by banner instead of platform MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Microsoft's OpenSSH port for Windows is the actually-buggy client — Cygwin, Git-Bash/MSYS2, and WSL ssh on Windows all have working ControlMaster. Probe `ssh -V` once at startup: only the Microsoft port prints "OpenSSH_for_Windows_", so we can skip multiplex narrowly for that one case and let everyone else use it. Also addresses review nits: * Trim the inline comment block (it now points at the helper instead of inlining the explanation). * Fix the options_to_add docstring — multiplex is known-broken on the Microsoft client, not "unsupported on this platform". * Add a test for "Windows + user has multiplex configured in ssh config" → we still respect the user's config and don't override it. Co-Authored-By: Claude Opus 4.7 (1M context) --- mama/utils/ssh_multiplex.py | 44 ++++++++--- .../test_ssh_multiplex/test_ssh_multiplex.py | 79 ++++++++++++++++++- 2 files changed, 110 insertions(+), 13 deletions(-) diff --git a/mama/utils/ssh_multiplex.py b/mama/utils/ssh_multiplex.py index 4a82335..509bb9f 100644 --- a/mama/utils/ssh_multiplex.py +++ b/mama/utils/ssh_multiplex.py @@ -53,6 +53,7 @@ _per_host_locks: dict[tuple, threading.Lock] = {} _warmed: dict[tuple, dict] = {} # (user, host, port) -> info _fetch_semaphore: threading.Semaphore | None = None +_buggy_ssh_cached: bool | None = None # URL parsing -------------------------------------------------------------- @@ -139,24 +140,47 @@ def is_multiplex_configured(probe: dict[str, str]) -> bool: return cm not in ('no', 'false', '') and cp not in ('none', '', 'no') +def multiplex_known_broken() -> bool: + """True when the active ssh has known-broken ControlMaster. + + Microsoft's OpenSSH port for Windows ships a flaky multiplex + implementation (master drops with "Connection reset by peer" and the + stale socket then blocks reattach). Its `ssh -V` banner is + `OpenSSH_for_Windows_` — Cygwin/MSYS/Git-Bash on Windows report + the standard `OpenSSH_p1` banner and work fine, so we let those + through. Result is cached for the process. + """ + global _buggy_ssh_cached + if not System.windows: + return False + if _buggy_ssh_cached is not None: + return _buggy_ssh_cached + with _state_lock: + if _buggy_ssh_cached is not None: + return _buggy_ssh_cached + try: + cp = subprocess.run(['ssh', '-V'], capture_output=True, + text=True, timeout=5) + # `ssh -V` writes to stderr on most builds; read both for safety. + banner = ((cp.stdout or '') + (cp.stderr or '')).lower() + _buggy_ssh_cached = 'for_windows' in banner + except (subprocess.TimeoutExpired, FileNotFoundError, OSError): + # Can't tell; on Windows default to the safe choice (skip mux). + _buggy_ssh_cached = True + return _buggy_ssh_cached + + def options_to_add(probe: dict[str, str]) -> tuple[list[str], bool]: """ Return (-o args, we_own_master). `we_own_master` is True when we are the one configuring multiplex (and therefore responsible for pre-warming and cleaning it up). False if the user already has multiplex configured, or - if multiplex is unsupported on this platform. + if multiplex is known-broken on this platform. """ opts: list[str] = [] we_own_master = False - # Windows OpenSSH (Microsoft port shipping in Win10/11) has unreliable - # ControlMaster support: the master commonly drops mid-session with - # "Connection reset by peer" and the stale socket file then blocks new - # multiplex attempts. Skip multiplex entirely on Windows; keepalives - # still work fine and parallel_load + the fetch semaphore continue to - # bound git concurrency. Users who want multiplex on Windows should - # configure it explicitly in ~/.ssh/config (e.g. via WSL or a Cygwin - # ssh) — we'll detect their config via `ssh -G` and respect it. - if not System.windows and not is_multiplex_configured(probe): + # Skip multiplex on Microsoft OpenSSH for Windows — see multiplex_known_broken. + if not multiplex_known_broken() and not is_multiplex_configured(probe): we_own_master = True os.makedirs(_OUR_CONTROL_DIR, mode=0o700, exist_ok=True) opts += [ diff --git a/tests/test_ssh_multiplex/test_ssh_multiplex.py b/tests/test_ssh_multiplex/test_ssh_multiplex.py index 0d97518..c0b6b1d 100644 --- a/tests/test_ssh_multiplex/test_ssh_multiplex.py +++ b/tests/test_ssh_multiplex/test_ssh_multiplex.py @@ -144,11 +144,11 @@ def test_user_has_multiplex_only(self): assert not any(o.startswith('-oControlPath=') for o in opts) assert any(o.startswith('-oServerAliveInterval=') for o in opts) - def test_windows_skips_multiplex_keeps_keepalives(self, monkeypatch, tmp_path): + def test_windows_microsoft_ssh_skips_multiplex_keeps_keepalives(self, monkeypatch, tmp_path): # Microsoft OpenSSH on Windows has unreliable ControlMaster — the # master drops mid-session and leaves the socket file behind. We - # disable multiplex on Windows entirely; keepalives are still useful. - monkeypatch.setattr(sm.System, 'windows', True) + # detect it via the "for_Windows" banner string and skip multiplex. + monkeypatch.setattr(sm, 'multiplex_known_broken', lambda: True) monkeypatch.setattr(sm, '_OUR_CONTROL_DIR', str(tmp_path / 'cm')) monkeypatch.setattr(sm, '_OUR_CONTROL_PATH', str(tmp_path / 'cm' / '%C')) probe = {'controlmaster': 'no', 'controlpath': 'none', @@ -161,6 +161,79 @@ def test_windows_skips_multiplex_keeps_keepalives(self, monkeypatch, tmp_path): assert any(o.startswith('-oServerAliveInterval=') for o in opts) assert any(o.startswith('-oServerAliveCountMax=') for o in opts) + def test_windows_cygwin_ssh_keeps_multiplex(self, monkeypatch, tmp_path): + # Cygwin/Git-Bash ssh on Windows reports the standard banner and has + # working ControlMaster — so we DO add multiplex even though we're + # on Windows. (Equivalent to "non-buggy ssh" in detection terms.) + monkeypatch.setattr(sm, 'multiplex_known_broken', lambda: False) + monkeypatch.setattr(sm, '_OUR_CONTROL_DIR', str(tmp_path / 'cm')) + monkeypatch.setattr(sm, '_OUR_CONTROL_PATH', str(tmp_path / 'cm' / '%C')) + probe = {'controlmaster': 'no', 'controlpath': 'none', + 'serveraliveinterval': '0'} + opts, we_own = sm.options_to_add(probe) + assert we_own is True + assert any(o.startswith('-oControlMaster=') for o in opts) + + def test_windows_user_configured_multiplex_respected(self, monkeypatch): + # Even when the active ssh is the buggy one, if the user has multiplex + # explicitly configured (e.g. via ~/.ssh/config pointing at Cygwin ssh) + # we must respect their config, not override it. + monkeypatch.setattr(sm, 'multiplex_known_broken', lambda: True) + probe = { + 'controlmaster': 'auto', 'controlpath': '~/.ssh/sockets/%C', + 'serveraliveinterval': '30', 'serveralivecountmax': '5', + } + opts, we_own = sm.options_to_add(probe) + assert we_own is False + assert opts == [], 'user has full config — we add nothing' + + +class TestMultiplexKnownBroken: + """`ssh -V` banner parsing for known-buggy clients.""" + + @pytest.fixture(autouse=True) + def _clear_cache(self, monkeypatch): + monkeypatch.setattr(sm, '_buggy_ssh_cached', None) + + def test_non_windows_never_broken(self, monkeypatch): + # On Linux/macOS we don't even probe — multiplex always works. + monkeypatch.setattr(sm.System, 'windows', False) + run_mock = mock.Mock() + with mock.patch('subprocess.run', run_mock): + assert sm.multiplex_known_broken() is False + run_mock.assert_not_called() + + def test_microsoft_for_windows_banner_detected(self, monkeypatch): + monkeypatch.setattr(sm.System, 'windows', True) + fake_cp = mock.Mock(returncode=0, stdout='', + stderr='OpenSSH_for_Windows_8.6p1, LibreSSL 3.4.3\n') + with mock.patch('subprocess.run', return_value=fake_cp): + assert sm.multiplex_known_broken() is True + + def test_cygwin_banner_not_broken(self, monkeypatch): + monkeypatch.setattr(sm.System, 'windows', True) + fake_cp = mock.Mock(returncode=0, stdout='', + stderr='OpenSSH_9.6p1, OpenSSL 3.0.13 30 Jan 2024\n') + with mock.patch('subprocess.run', return_value=fake_cp): + assert sm.multiplex_known_broken() is False + + def test_result_is_cached(self, monkeypatch): + monkeypatch.setattr(sm.System, 'windows', True) + fake_cp = mock.Mock(returncode=0, stdout='', + stderr='OpenSSH_for_Windows_8.6p1\n') + with mock.patch('subprocess.run', return_value=fake_cp) as run: + sm.multiplex_known_broken() + sm.multiplex_known_broken() + sm.multiplex_known_broken() + assert run.call_count == 1 + + def test_ssh_missing_treated_as_broken_on_windows(self, monkeypatch): + # Conservative default: if we can't even invoke ssh, don't risk + # configuring multiplex on Windows. + monkeypatch.setattr(sm.System, 'windows', True) + with mock.patch('subprocess.run', side_effect=FileNotFoundError): + assert sm.multiplex_known_broken() is True + class TestProbeSshConfig: def test_parses_keys(self): From 21f573654c75e1a7132fdfb868bf82ff3511c7f4 Mon Sep 17 00:00:00 2001 From: Mark Kuckian Date: Tue, 5 May 2026 11:14:29 +0300 Subject: [PATCH 4/5] ssh_multiplex: tighten multiplex_known_broken MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Drop double-checked locking — racing initialisers both write the same bool, the lock was paranoia. * Drop `or ''` defensive null checks — subprocess.run(text=True) returns strings, not None. * Compress the docstring to 4 lines (matching this file's terse-helper style for sibling helpers like is_multiplex_configured). * Drop the inline call-site comment — the function name says it. 13 lines lighter, same behaviour, all 41 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- mama/utils/ssh_multiplex.py | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/mama/utils/ssh_multiplex.py b/mama/utils/ssh_multiplex.py index 509bb9f..7c1c657 100644 --- a/mama/utils/ssh_multiplex.py +++ b/mama/utils/ssh_multiplex.py @@ -141,33 +141,21 @@ def is_multiplex_configured(probe: dict[str, str]) -> bool: def multiplex_known_broken() -> bool: - """True when the active ssh has known-broken ControlMaster. - - Microsoft's OpenSSH port for Windows ships a flaky multiplex - implementation (master drops with "Connection reset by peer" and the - stale socket then blocks reattach). Its `ssh -V` banner is - `OpenSSH_for_Windows_` — Cygwin/MSYS/Git-Bash on Windows report - the standard `OpenSSH_p1` banner and work fine, so we let those - through. Result is cached for the process. - """ + """True iff the active ssh is Microsoft's OpenSSH for Windows, whose + ControlMaster is flaky (master drops, stale socket blocks reattach). + Detected via the `OpenSSH_for_Windows_` banner; Cygwin/MSYS/Git-Bash + on Windows report the standard banner and work fine. Cached per process.""" global _buggy_ssh_cached if not System.windows: return False - if _buggy_ssh_cached is not None: - return _buggy_ssh_cached - with _state_lock: - if _buggy_ssh_cached is not None: - return _buggy_ssh_cached + if _buggy_ssh_cached is None: try: cp = subprocess.run(['ssh', '-V'], capture_output=True, text=True, timeout=5) - # `ssh -V` writes to stderr on most builds; read both for safety. - banner = ((cp.stdout or '') + (cp.stderr or '')).lower() - _buggy_ssh_cached = 'for_windows' in banner + _buggy_ssh_cached = 'for_windows' in (cp.stdout + cp.stderr).lower() except (subprocess.TimeoutExpired, FileNotFoundError, OSError): - # Can't tell; on Windows default to the safe choice (skip mux). - _buggy_ssh_cached = True - return _buggy_ssh_cached + _buggy_ssh_cached = True # can't tell — be safe + return _buggy_ssh_cached def options_to_add(probe: dict[str, str]) -> tuple[list[str], bool]: @@ -179,7 +167,6 @@ def options_to_add(probe: dict[str, str]) -> tuple[list[str], bool]: """ opts: list[str] = [] we_own_master = False - # Skip multiplex on Microsoft OpenSSH for Windows — see multiplex_known_broken. if not multiplex_known_broken() and not is_multiplex_configured(probe): we_own_master = True os.makedirs(_OUR_CONTROL_DIR, mode=0o700, exist_ok=True) From 18dabd0376ebb82dc6cc6432ada49da6747a4282 Mon Sep 17 00:00:00 2001 From: Mark Kuckian Date: Tue, 5 May 2026 11:31:44 +0300 Subject: [PATCH 5/5] fix: stop mama_ssh.py shadowing stdlib `types` via sys.path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When git invokes the wrapper as `python /path/to/mama_ssh.py`, __package__ is empty and the script took a fallback path that did: sys.path.insert(0, os.path.dirname(_THIS_DIR)) # = .../mama from utils import ssh_multiplex That puts the `mama/` package dir at the front of sys.path. Inside `mama/` lives a `types/` subpackage (mama.types — git/asset/dep_source etc.), which then shadows Python's stdlib `types`. The next thing that does `from types import MethodType, GenericAlias` (e.g. `contextlib`, transitively imported by ssh_multiplex.py) explodes: ImportError: cannot import name 'MethodType' from 'types' (...site-packages/mama/types/__init__.py) This bit Windows users on uv-installed Pythons where `contextlib` hadn't been pre-cached before our path manipulation ran. Fix: try the qualified `from mama.utils import ssh_multiplex` first (works for any pip-installed mama, no path manipulation needed). Only fall back to sys.path manipulation if that fails, and add the GRANDPARENT of mama_ssh.py — i.e. the dir that contains `mama/` — so `import mama.` resolves but `mama/types/` never appears as a top-level import root. Adds a regression test that runs the wrapper in a fresh subprocess and asserts the `mama/` dir does not appear on sys.path. Co-Authored-By: Claude Opus 4.7 (1M context) --- mama/utils/mama_ssh.py | 14 +++++-- .../test_ssh_multiplex/test_ssh_multiplex.py | 40 +++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/mama/utils/mama_ssh.py b/mama/utils/mama_ssh.py index dedfc70..944e22f 100755 --- a/mama/utils/mama_ssh.py +++ b/mama/utils/mama_ssh.py @@ -21,10 +21,18 @@ import sys # Allow running as a standalone script, not just as a package module. -_THIS_DIR = os.path.dirname(os.path.abspath(__file__)) +# Important: do NOT put `<...>/mama` on sys.path — `mama/types/` would then +# shadow Python's stdlib `types` module the moment anything (e.g. contextlib) +# does `from types import ...`. Add the package's PARENT instead, so that +# `mama.utils.ssh_multiplex` resolves as a normal qualified import. if __package__ in (None, ''): - sys.path.insert(0, os.path.dirname(_THIS_DIR)) - from utils import ssh_multiplex # type: ignore + try: + from mama.utils import ssh_multiplex + except ImportError: + _MAMA_PARENT = os.path.dirname(os.path.dirname( + os.path.dirname(os.path.abspath(__file__)))) + sys.path.insert(0, _MAMA_PARENT) + from mama.utils import ssh_multiplex else: from . import ssh_multiplex diff --git a/tests/test_ssh_multiplex/test_ssh_multiplex.py b/tests/test_ssh_multiplex/test_ssh_multiplex.py index c0b6b1d..158789f 100644 --- a/tests/test_ssh_multiplex/test_ssh_multiplex.py +++ b/tests/test_ssh_multiplex/test_ssh_multiplex.py @@ -360,6 +360,46 @@ def worker(): assert probe_count[0] == 1 +class TestWrapperPathSafety: + """Regression: running mama_ssh.py as a script must not shadow stdlib + modules. Earlier versions inserted `<...>/mama` onto sys.path, which made + `mama/types/` shadow Python's stdlib `types` module — breaking `contextlib` + on uv-installed Pythons that hadn't pre-imported it.""" + + def test_invocation_does_not_put_mama_dir_on_syspath(self, tmp_path): + import json + import subprocess + import textwrap + wrapper = os.path.abspath(os.path.join( + os.path.dirname(__file__), '..', '..', 'mama', 'utils', 'mama_ssh.py')) + mama_dir = os.path.dirname(os.path.dirname(wrapper)) + # Subprocess so we get a fresh interpreter (no pre-cached `types` etc). + # Monkey-patch os.execvp to a no-op BEFORE running the wrapper, so it + # can't replace the process before we read sys.path back. + probe = tmp_path / 'probe.py' + probe.write_text(textwrap.dedent(f""" + import json, os, sys + os.execvp = lambda *a, **k: None + sys.argv = [{wrapper!r}, 'git@example.com:foo.git', 'git-upload-pack'] + ns = {{'__name__': '__main__', '__package__': '', '__file__': {wrapper!r}}} + with open({wrapper!r}) as f: + code = f.read() + try: + exec(code, ns) + except SystemExit: + pass + print('PATH_PROBE:' + json.dumps(sys.path)) + """)) + cp = subprocess.run([sys.executable, str(probe)], + capture_output=True, text=True, timeout=15) + marker = [l for l in cp.stdout.splitlines() if l.startswith('PATH_PROBE:')] + assert marker, f'probe did not produce output. stderr={cp.stderr!r}' + path = json.loads(marker[-1][len('PATH_PROBE:'):]) + assert mama_dir not in path, ( + f'{mama_dir!r} ended up on sys.path — `mama/types/` would shadow ' + f'stdlib `types`. sys.path={path!r}') + + class TestWrapperMain: """The wrapper passes options + destination unchanged to ssh -G, then exec's ssh with whatever extra -o flags are needed."""