diff --git a/hermes_cli/main.py b/hermes_cli/main.py index 7d4a4a924..72d660bac 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -3022,33 +3022,19 @@ def _restore_stashed_changes( print("\nYour stashed changes are preserved — nothing is lost.") print(f" Stash ref: {stash_ref}") - # Ask before resetting (if interactive) - do_reset = True - if prompt_user: - print("\nReset working tree to clean state so Hermes can run?") - print(" (You can re-apply your changes later with: git stash apply)") - print("[Y/n] ", end="", flush=True) - response = input().strip().lower() - if response not in ("", "y", "yes"): - do_reset = False - - if do_reset: - subprocess.run( - git_cmd + ["reset", "--hard", "HEAD"], - cwd=cwd, - capture_output=True, - ) - print("Working tree reset to clean state.") - else: - print("Working tree left as-is (may have conflict markers).") - print("Resolve conflicts manually, then run: git stash drop") - - print(f"Restore your changes with: git stash apply {stash_ref}") - # In non-interactive mode (gateway /update), don't abort — the code - # update itself succeeded, only the stash restore had conflicts. - # Aborting would report the entire update as failed. - if prompt_user: - sys.exit(1) + # Always reset to clean state — leaving conflict markers in source + # files makes hermes completely unrunnable (SyntaxError on import). + # The user's changes are safe in the stash for manual recovery. + subprocess.run( + git_cmd + ["reset", "--hard", "HEAD"], + cwd=cwd, + capture_output=True, + ) + print("Working tree reset to clean state.") + print(f"Restore your changes later with: git stash apply {stash_ref}") + # Don't sys.exit — the code update itself succeeded, only the stash + # restore had conflicts. Let cmd_update continue with pip install, + # skill sync, and gateway restart. return False stash_selector = _resolve_stash_selector(git_cmd, cwd, stash_ref) diff --git a/tests/hermes_cli/test_update_autostash.py b/tests/hermes_cli/test_update_autostash.py index f97c6c35f..dee8cc1fb 100644 --- a/tests/hermes_cli/test_update_autostash.py +++ b/tests/hermes_cli/test_update_autostash.py @@ -213,8 +213,12 @@ def test_restore_stashed_changes_keeps_going_when_drop_fails(monkeypatch, tmp_pa assert "git stash drop stash@{0}" in out -def test_restore_stashed_changes_prompts_before_reset_on_conflict(monkeypatch, tmp_path, capsys): - """When conflicts occur interactively, user is prompted before reset.""" +def test_restore_stashed_changes_always_resets_on_conflict(monkeypatch, tmp_path, capsys): + """Conflicts always auto-reset (no prompt) and return False, even interactively. + + Leaving conflict markers in source files makes hermes unrunnable (SyntaxError). + The stash is preserved for manual recovery; cmd_update continues normally. + """ calls = [] def fake_run(cmd, **kwargs): @@ -230,45 +234,19 @@ def test_restore_stashed_changes_prompts_before_reset_on_conflict(monkeypatch, t monkeypatch.setattr(hermes_main.subprocess, "run", fake_run) monkeypatch.setattr("builtins.input", lambda: "y") - with pytest.raises(SystemExit, match="1"): - hermes_main._restore_stashed_changes(["git"], tmp_path, "abc123", prompt_user=True) + result = hermes_main._restore_stashed_changes(["git"], tmp_path, "abc123", prompt_user=True) + assert result is False out = capsys.readouterr().out assert "Conflicted files:" in out assert "hermes_cli/main.py" in out assert "stashed changes are preserved" in out - assert "Reset working tree to clean state" in out assert "Working tree reset to clean state" in out + assert "git stash apply abc123" in out reset_calls = [c for c, _ in calls if c[1:3] == ["reset", "--hard"]] assert len(reset_calls) == 1 -def test_restore_stashed_changes_user_declines_reset(monkeypatch, tmp_path, capsys): - """When user declines reset, working tree is left as-is.""" - calls = [] - - def fake_run(cmd, **kwargs): - calls.append((cmd, kwargs)) - if cmd[1:3] == ["stash", "apply"]: - return SimpleNamespace(stdout="", stderr="conflict\n", returncode=1) - if cmd[1:3] == ["diff", "--name-only"]: - return SimpleNamespace(stdout="cli.py\n", stderr="", returncode=0) - raise AssertionError(f"unexpected command: {cmd}") - - monkeypatch.setattr(hermes_main.subprocess, "run", fake_run) - # First input: "y" to restore, second input: "n" to decline reset - inputs = iter(["y", "n"]) - monkeypatch.setattr("builtins.input", lambda: next(inputs)) - - with pytest.raises(SystemExit, match="1"): - hermes_main._restore_stashed_changes(["git"], tmp_path, "abc123", prompt_user=True) - - out = capsys.readouterr().out - assert "left as-is" in out - reset_calls = [c for c, _ in calls if c[1:3] == ["reset", "--hard"]] - assert len(reset_calls) == 0 - - def test_restore_stashed_changes_auto_resets_non_interactive(monkeypatch, tmp_path, capsys): """Non-interactive mode auto-resets without prompting and returns False instead of sys.exit(1) so the update can continue (gateway /update path)."""