diff --git a/tests/hermes_cli/test_doctor.py b/tests/hermes_cli/test_doctor.py index c9df041d064..b3506aa348b 100644 --- a/tests/hermes_cli/test_doctor.py +++ b/tests/hermes_cli/test_doctor.py @@ -1406,3 +1406,62 @@ class TestDoctorStaleMaxIterationsDrift: monkeypatch, tmp_path, fix=False, ghost=None, cfg_turns=400, ) assert "shadows" not in out + + +def test_npm_audit_fix_hint_avoids_crashing_workspace_flag(monkeypatch, tmp_path): + """`hermes doctor` must not hand users `npm audit fix --workspace `: + that exact form crashes npm with "Cannot read properties of null (reading + 'edgesOut')" (an arborist bug with workspace-filtered audit fix). The + remediation hint for a workspace vulnerability must be the root-level + `npm audit fix`, which works. + + Regression for the Docker reports (Pinched-Nerve / lynch1972) where doctor + flagged the web/ui-tui workspaces and the suggested fix command errored out. + """ + home = tmp_path / ".hermes" + home.mkdir(parents=True, exist_ok=True) + project = tmp_path / "project" + (project / "node_modules").mkdir(parents=True) + + monkeypatch.setenv("HERMES_HOME", str(home)) + monkeypatch.setattr(doctor_mod, "HERMES_HOME", home) + monkeypatch.setattr(doctor_mod, "PROJECT_ROOT", project) + + # Only npm is "installed" — keeps the rest of run_doctor's external checks + # quiet without affecting the npm-audit branch under test. + monkeypatch.setattr( + doctor_mod.shutil, "which", lambda cmd: "/usr/bin/npm" if cmd == "npm" else None + ) + + def mock_run(cmd, **kwargs): + if "audit" in cmd: + payload = ( + '{"metadata": {"vulnerabilities": ' + '{"critical": 0, "high": 2, "moderate": 0}}}' + ) + return SimpleNamespace(returncode=1, stdout=payload, stderr="") + return SimpleNamespace(returncode=0, stdout="", stderr="") + + import subprocess + + monkeypatch.setattr(subprocess, "run", mock_run) + + buf = io.StringIO() + with contextlib.redirect_stdout(buf): + doctor_mod.run_doctor(Namespace(fix=False)) + out = buf.getvalue() + + # The workspace vulnerability is still reported ... + assert "web workspace" in out + # ... but the remediation must NOT use the npm-crashing per-workspace form + # (`npm audit fix --workspace web` / `--workspace ui-tui`). The unrelated + # root target's `--workspaces=false` is a different, non-crashing command. + assert "npm audit fix --workspace web" not in out + assert "npm audit fix --workspace ui-tui" not in out + # ... it offers the safe root-level command instead. + assert "&& npm audit fix)" in out + # ... and explains the workspace advisories are build-time tooling whose + # `npm audit fix` may itself hit a known npm arborist crash, so the user + # isn't left thinking a crashing command means a broken Hermes install. + assert "build-time tooling" in out + assert "known npm bug" in out