fix(doctor): resolve managed/legacy agent-browser dirs PATHEXT-aware

Follow-up on the #53205 salvage: replace bare is_file() probes of the
managed (~/.hermes/node[/bin]) and legacy (node_modules/.bin) locations
with shutil.which(..., path=dir) so Windows resolves the executable
.cmd shim instead of the extensionless POSIX script — the same miss
class fixed for _has_agent_browser() in #73932. Also covers the
Windows managed layout where the binary sits in node/ directly.
This commit is contained in:
Teknium 2026-07-29 00:04:10 -07:00
parent 1cec6afdfc
commit c892ca25e0
3 changed files with 32 additions and 10 deletions

View file

@ -1817,18 +1817,32 @@ def run_doctor(args):
# Hermes-managed node prefix, which isn't necessarily on PATH. Mirror
# dep_ensure._has_hermes_agent_browser() so doctor and dep_ensure agree
# on what "installed" means; otherwise doctor false-negatives (#53192).
_managed_ab = HERMES_HOME / "node" / "bin" / "agent-browser"
_legacy_ab = HERMES_HOME / "node_modules" / ".bin" / "agent-browser"
# Resolve with PATHEXT-aware ``shutil.which`` (not a bare is_file())
# so Windows picks the executable ``.cmd`` shim — the same class of
# miss fixed for _has_agent_browser() in #73932.
def _which_in(directory) -> str | None:
try:
if not directory.is_dir():
return None
return shutil.which("agent-browser", path=str(directory))
except Exception:
return None
_managed_ab = (
_which_in(HERMES_HOME / "node" / "bin")
or _which_in(HERMES_HOME / "node")
)
_legacy_ab = _which_in(HERMES_HOME / "node_modules" / ".bin")
if agent_browser_path.exists():
check_ok("agent-browser (Node.js)", "(browser automation)")
agent_browser_ok = True
elif _which_ab and agent_browser_runnable(_which_ab):
check_ok("agent-browser", "(browser automation)")
agent_browser_ok = True
elif _managed_ab.is_file() and agent_browser_runnable(str(_managed_ab)):
elif _managed_ab and agent_browser_runnable(_managed_ab):
check_ok("agent-browser", "(browser automation)")
agent_browser_ok = True
elif _legacy_ab.is_file() and agent_browser_runnable(str(_legacy_ab)):
elif _legacy_ab and agent_browser_runnable(_legacy_ab):
check_ok("agent-browser", "(browser automation)")
agent_browser_ok = True
elif _which_ab:

View file

@ -751,6 +751,7 @@ def _run_doctor_with_managed_agent_browser(monkeypatch, tmp_path, runnable):
(home / "config.yaml").write_text("memory: {}\n", encoding="utf-8")
managed_ab = home / "node" / "bin" / "agent-browser"
managed_ab.write_text("#!/bin/sh\n", encoding="utf-8")
managed_ab.chmod(0o755)
project = tmp_path / "project"
project.mkdir(exist_ok=True) # no node_modules/agent-browser here
@ -759,12 +760,18 @@ def _run_doctor_with_managed_agent_browser(monkeypatch, tmp_path, runnable):
monkeypatch.setattr(doctor_mod, "HERMES_HOME", home)
monkeypatch.setattr(doctor_mod, "PROJECT_ROOT", project)
monkeypatch.setattr(doctor_mod, "_DHH", str(home))
# node on PATH, agent-browser is NOT on PATH (only in the managed bin)
monkeypatch.setattr(
doctor_mod.shutil,
"which",
lambda cmd: "/usr/bin/node" if cmd in {"node", "npm"} else None,
)
# node on PATH, agent-browser is NOT on PATH (only in the managed bin).
# The managed-dir rung resolves via shutil.which(..., path=<dir>) so
# Windows picks the .cmd shim — mirror that shape here.
def _fake_which(cmd, path=None):
if path is not None:
if cmd == "agent-browser" and str(managed_ab.parent) == str(path):
return str(managed_ab)
return None
return "/usr/bin/node" if cmd in {"node", "npm"} else None
monkeypatch.setattr(doctor_mod.shutil, "which", _fake_which)
# agent_browser_runnable is imported into doctor's namespace
monkeypatch.setattr(
doctor_mod,