diff --git a/agent/conversation_loop.py b/agent/conversation_loop.py index fae6ff2e6950..0c0660e9cdcc 100644 --- a/agent/conversation_loop.py +++ b/agent/conversation_loop.py @@ -559,6 +559,20 @@ def _stored_prompt_matches_runtime(agent, prompt: str) -> bool: if stored_provider and current_provider and stored_provider != current_provider: return False + # Detect cwd drift: if the stored prompt was built in a different working + # directory, reuse would silently inject a stale path into the prefix cache. + stored_cwd = line_value("Current working directory") + if stored_cwd: + import os as _os + if stored_cwd != _os.getcwd(): + return False + + # Detect runtime surface drift: desktop GUI vs non-desktop. + has_desktop_marker = "Runtime surface: you're running inside the Hermes desktop GUI app." in prompt + in_desktop = (os.environ.get("HERMES_DESKTOP", "").strip().lower() in {"1", "true", "yes"}) + if has_desktop_marker != in_desktop: + return False + return True diff --git a/tests/run_agent/test_compression_persistence.py b/tests/run_agent/test_compression_persistence.py index 9f0f6e70fa5f..4532b84de9f3 100644 --- a/tests/run_agent/test_compression_persistence.py +++ b/tests/run_agent/test_compression_persistence.py @@ -437,3 +437,51 @@ class TestGatewayHistoryOffsetAfterSplit: assert len(new_messages) == 0, ( "Expected 0 messages with stale offset=200 (demonstrates the bug)" ) + + +class TestStoredPromptCwdDrift: + """Verify that stored system prompts are rejected when cwd changed.""" + + def _make_agent(self, model="test/model", provider="openrouter"): + class _Agent: + pass + + agent = _Agent() + agent.model = model + agent.provider = provider + return agent + + def test_stored_prompt_stale_when_cwd_differs(self): + """Different cwd should force a prompt rebuild.""" + from unittest.mock import patch + from agent.conversation_loop import _stored_prompt_matches_runtime + + agent = self._make_agent() + stored_prompt = ( + "Current working directory: /project/old\n" + "Model: test/model\n" + "Provider: openrouter\n" + ) + + with patch("os.getcwd", return_value="/project/new"): + assert _stored_prompt_matches_runtime(agent, stored_prompt) is False, ( + "Expected False when stored cwd differs from current cwd" + ) + + def test_stored_prompt_fresh_when_cwd_matches(self): + """Matching cwd should allow prompt reuse.""" + from unittest.mock import patch + from agent.conversation_loop import _stored_prompt_matches_runtime + + agent = self._make_agent() + current_cwd = "/project/current" + stored_prompt = ( + f"Current working directory: {current_cwd}\n" + "Model: test/model\n" + "Provider: openrouter\n" + ) + + with patch("os.getcwd", return_value=current_cwd): + assert _stored_prompt_matches_runtime(agent, stored_prompt) is True, ( + "Expected True when stored cwd matches current cwd" + )