Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7032,6 +7032,14 @@ def _handle_resume_command(self, cmd_original: str) -> None:
else:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix direction is correct, but this handler has moved on current main. Transplant this call after the display branches in hermes_cli/cli_commands_mixin.py:825; /sessions <id> still delegates to that handler.

_cprint(f" ↻ Resumed session {target_id}{title_part} — no messages, starting fresh.")

# Retarget the process + tool cwd to where the session was started, so a
# mid-chat /resume (and /sessions <id>, which delegates here) lands in the
# same directory as a startup `hermes -c`/`--resume`. The startup resume
# paths already call this; without it, the terminal/code-exec tools and
# relative-path resolution keep operating in the wrong repo. Idempotent
# and a no-op when the session recorded no cwd. See #38562.
self._restore_session_cwd(session_meta)

def _consume_pending_resume_selection(self, text: str) -> bool:
"""Resolve a bare numeric reply that follows a bare ``/resume`` prompt.

Expand Down
73 changes: 73 additions & 0 deletions tests/cli/test_cli_resume_command.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
from unittest.mock import MagicMock, patch

from cli import HermesCLI
Expand Down Expand Up @@ -119,6 +120,78 @@ def test_handle_resume_does_not_strip_partial_brackets(self):
assert "<half" in printed


class TestCliResumeRestoresCwd:
"""Mid-chat /resume must retarget the working directory to where the
session was started — the same contract as a startup ``hermes -c`` /
``--resume``.

Regression coverage for #38562: ``_restore_session_cwd()`` was wired into
the startup resume paths but not into ``_handle_resume_command()``, so an
interactive ``/resume`` (and ``/sessions <id>``, which delegates here) left
the process + ``TERMINAL_CWD`` pointing at whatever directory the user had
cd'd into — so the terminal/code-exec tools and relative paths ran in the
wrong repo.
"""

def _resumable_cli(self, session_meta):
cli_obj = _make_cli()
cli_obj._session_db.get_session.return_value = session_meta
cli_obj._session_db.get_messages_as_conversation.return_value = [
{"role": "user", "content": "hello"},
]
cli_obj._session_db.resolve_resume_session_id.return_value = session_meta["id"]
return cli_obj

def test_handle_resume_restores_recorded_cwd(self, tmp_path):
recorded = str(tmp_path)
cli_obj = self._resumable_cli({"id": "sess_dir", "title": "Dir", "cwd": recorded})

with (
patch("hermes_cli.main._resolve_session_by_name_or_id", return_value="sess_dir"),
patch("cli._cprint"),
patch.object(cli_obj, "_console_print"),
patch("os.chdir") as mock_chdir,
patch.dict(os.environ, {}, clear=False),
):
cli_obj._handle_resume_command("/resume Dir")
# Assert inside the patch.dict scope — it restores os.environ on exit.
assert os.environ.get("TERMINAL_CWD") == recorded

mock_chdir.assert_called_once_with(recorded)

def test_handle_resume_without_recorded_cwd_does_not_chdir(self):
# Gateway/remote/older sessions record no cwd — restore must no-op.
cli_obj = self._resumable_cli({"id": "sess_dir", "title": "Dir"})

with (
patch("hermes_cli.main._resolve_session_by_name_or_id", return_value="sess_dir"),
patch("cli._cprint"),
patch.object(cli_obj, "_console_print"),
patch("os.chdir") as mock_chdir,
):
cli_obj._handle_resume_command("/resume Dir")

mock_chdir.assert_not_called()

def test_sessions_command_restores_recorded_cwd(self, tmp_path):
# /sessions <id> delegates to the resume flow, so it restores cwd too.
recorded = str(tmp_path)
cli_obj = self._resumable_cli({"id": "sess_dir", "title": "Dir", "cwd": recorded})

with (
patch("hermes_cli.main._resolve_session_by_name_or_id", return_value="sess_dir"),
patch("cli._cprint"),
patch.object(cli_obj, "_console_print"),
patch("os.chdir") as mock_chdir,
patch.dict(os.environ, {}, clear=False),
):
cli_obj._handle_sessions_command("/sessions Dir")
# Assert inside the patch.dict scope — it restores os.environ on exit.
assert os.environ.get("TERMINAL_CWD") == recorded

mock_chdir.assert_called_once_with(recorded)


class TestPendingResumeNumberedSelection:
"""Bare `/resume` arms a one-shot prompt so the next bare number resumes.

Expand Down
Loading