diff --git a/hermes_cli/main.py b/hermes_cli/main.py index 5744d8a9e39..4911de19a7c 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -4609,13 +4609,18 @@ def _run_npm_install_deterministic( the working tree dirty and causes the next ``hermes update`` to stash the lockfile — repeatedly. """ + # unicode-animations' postinstall animates to /dev/tty (bypasses + # --silent/capture_output). It no-ops when CI is set — same as the TUI + # install path and nix/lib.nix npm ci hooks. + run_env = {**os.environ, **(env or {}), "CI": "1"} + lockfile = cwd / "package-lock.json" if lockfile.exists(): ci_cmd = [npm, "ci", *extra_args] ci_result = subprocess.run( ci_cmd, cwd=cwd, - env=env, + env=run_env, capture_output=capture_output, text=True, encoding="utf-8", @@ -4630,7 +4635,7 @@ def _run_npm_install_deterministic( return subprocess.run( install_cmd, cwd=cwd, - env=env, + env=run_env, capture_output=capture_output, text=True, encoding="utf-8", diff --git a/tests/hermes_cli/test_web_ui_build.py b/tests/hermes_cli/test_web_ui_build.py index cc20fa61cf3..8f94458a6ac 100644 --- a/tests/hermes_cli/test_web_ui_build.py +++ b/tests/hermes_cli/test_web_ui_build.py @@ -140,6 +140,22 @@ class TestBuildWebUISkipsWhenFresh: assert kwargs["encoding"] == "utf-8" assert kwargs["errors"] == "replace" + def test_npm_install_sets_ci_to_suppress_postinstall_tty_output(self, tmp_path): + web_dir, _ = _make_web_dir(tmp_path) + (web_dir / "package-lock.json").write_text("{}", encoding="utf-8") + + mock_cp = __import__("subprocess").CompletedProcess([], 0, stdout="", stderr="") + with patch("hermes_cli.main.subprocess.run", return_value=mock_cp) as mock_run: + _run_npm_install_deterministic( + "/usr/bin/npm", + web_dir, + env={"PYTHON": "/nix/store/python"}, + ) + + _, kwargs = mock_run.call_args + assert kwargs["env"]["CI"] == "1" + assert kwargs["env"]["PYTHON"] == "/nix/store/python" + def test_npm_install_uses_workspace_web_scope(self, tmp_path): web_dir, _ = _make_web_dir(tmp_path) # Real workspace checkout: the single lockfile lives at the root, so