fix(cli): ignore stale HERMES_TUI_RESUME env

HERMES_TUI_RESUME is an internal env var the Python wrapper exports to hand
a session ID off to the Ink TUI. Because _launch_tui started from
os.environ.copy(), any exported/stale value in the user's shell leaked
through — so plain `hermes --tui` would try to resume a missing session
and leave the UI at 'error: session not found' with no live session.

Drop HERMES_TUI_RESUME from the env before conditionally re-setting it
from the argparse-resolved resume_session_id. Tests cover both the drop
path and the set-from-arg path.

Salvage of #28080 by @noctilust.
This commit is contained in:
noctilust
2026-05-19 00:10:09 -07:00
committed by Teknium
parent afffb8d9a5
commit 425aba766b
2 changed files with 50 additions and 0 deletions
+8
View File
@@ -1278,6 +1278,14 @@ def _launch_tui(
if "--expose-gc" not in _tokens:
_tokens.append("--expose-gc")
env["NODE_OPTIONS"] = " ".join(_tokens)
# HERMES_TUI_RESUME is an internal hand-off from the Python wrapper to the
# Ink app. Because we start from os.environ.copy(), an exported/stale value
# in the user's shell would otherwise make a plain `hermes --tui` try to
# resume a non-existent session and leave the UI at "error: session not
# found" with no live session. Only forward a resume id that argparse
# resolved for this invocation; direct `node ui-tui/dist/entry.js` users can
# still set HERMES_TUI_RESUME themselves.
env.pop("HERMES_TUI_RESUME", None)
if resume_session_id:
env["HERMES_TUI_RESUME"] = resume_session_id
+42
View File
@@ -541,6 +541,48 @@ def test_launch_tui_exit_code_42_relaunches_update(monkeypatch, main_mod):
mock_relaunch.assert_called_once_with(["update"], preserve_inherited=False)
def test_launch_tui_drops_stale_resume_env_without_resume_arg(monkeypatch, main_mod):
captured = {}
monkeypatch.setenv("HERMES_TUI_RESUME", "stale-missing-session")
monkeypatch.setattr(
main_mod,
"_make_tui_argv",
lambda tui_dir, tui_dev: (["node", "dist/entry.js"], Path(".")),
)
monkeypatch.setattr(
main_mod.subprocess,
"call",
lambda argv, cwd=None, env=None: captured.update({"env": env}) or 1,
)
with pytest.raises(SystemExit):
main_mod._launch_tui()
assert "HERMES_TUI_RESUME" not in captured["env"]
def test_launch_tui_sets_resume_env_from_resume_arg(monkeypatch, main_mod):
captured = {}
monkeypatch.setenv("HERMES_TUI_RESUME", "stale-missing-session")
monkeypatch.setattr(
main_mod,
"_make_tui_argv",
lambda tui_dir, tui_dev: (["node", "dist/entry.js"], Path(".")),
)
monkeypatch.setattr(
main_mod.subprocess,
"call",
lambda argv, cwd=None, env=None: captured.update({"env": env}) or 1,
)
with pytest.raises(SystemExit):
main_mod._launch_tui(resume_session_id="20260518_000000_goodid")
assert captured["env"]["HERMES_TUI_RESUME"] == "20260518_000000_goodid"
def test_make_tui_argv_dev_prebuilds_hermes_ink(monkeypatch, main_mod, tmp_path):
tui_dir = tmp_path / "ui-tui"
tsx = tui_dir / "node_modules" / ".bin" / "tsx"