From 7aa7118e645388e7d19c2cfa87d00f41fe005ad9 Mon Sep 17 00:00:00 2001 From: abrichr Date: Tue, 20 Oct 2026 12:32:14 -0400 Subject: [PATCH] fix(cli): the PATH version check never runs inside the check it starts `openadapt --version` asks any other openadapt on PATH for its version, and that one ran the same check, asking the first back. With two installs that both carry the check, they recursed without bound (a fork bomb, load ~900 on 2026-10-20). The child now runs with OPENADAPT_NO_LAUNCHER_CHECK=1, and the check returns at once when it is set. Co-Authored-By: Claude Opus 5.5 --- openadapt/cli.py | 14 +++++++++++++- tests/test_cli_uvx_hints.py | 23 +++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/openadapt/cli.py b/openadapt/cli.py index 9f00b71f5..dff42a552 100644 --- a/openadapt/cli.py +++ b/openadapt/cli.py @@ -718,7 +718,11 @@ def _launcher_version(path: str, run=None) -> Optional[str]: try: result = (run or subprocess.run)( - [path, "--version"], capture_output=True, text=True, timeout=15 + [path, "--version"], + capture_output=True, + text=True, + timeout=15, + env={**os.environ, _NO_LAUNCHER_CHECK: "1"}, ) text = f"{result.stdout} {result.stderr}" except Exception: # noqa: BLE001 - an unrunnable openadapt has no version @@ -824,7 +828,15 @@ def _launcher_conflicts(launchers=None, run=None) -> list[str]: return lines +#: Set in the environment of every ``openadapt --version`` this check runs, so +#: the child never runs the check itself. Without it, two installs that both +#: carry the check ask each other for their version forever. +_NO_LAUNCHER_CHECK = "OPENADAPT_NO_LAUNCHER_CHECK" + + def _warn_about_launcher_conflicts() -> None: + if os.environ.get(_NO_LAUNCHER_CHECK): + return try: lines = _launcher_conflicts() except Exception: # noqa: BLE001 - a PATH check must never break a command diff --git a/tests/test_cli_uvx_hints.py b/tests/test_cli_uvx_hints.py index 9c04ba90d..fc9949dd0 100644 --- a/tests/test_cli_uvx_hints.py +++ b/tests/test_cli_uvx_hints.py @@ -112,3 +112,26 @@ def test_the_finale_prints_keep_it_and_the_shadow_warning( ) finale = lines[lines.index("Receipt") :] assert len(finale) <= 12 + + +def test_version_check_never_recurses(monkeypatch) -> None: + """The child ``openadapt --version`` gets a guard, and the guard stops the check.""" + seen = {} + + def fake_run(args, **kwargs): + seen["env"] = kwargs.get("env") or {} + + class R: + stdout = "openadapt, version 0.0.1" + stderr = "" + + return R() + + assert cli._launcher_version("/x/openadapt", fake_run) == "0.0.1" + assert seen["env"].get(cli._NO_LAUNCHER_CHECK) == "1" + + called = [] + monkeypatch.setenv(cli._NO_LAUNCHER_CHECK, "1") + monkeypatch.setattr(cli, "_launcher_conflicts", lambda: called.append(1) or []) + cli._warn_about_launcher_conflicts() + assert called == []