From eed6bb14bcb94b9ea5f9f7fd7fbe31a2fdb3ab09 Mon Sep 17 00:00:00 2001 From: Ethan <23321960+0xLeathery@users.noreply.github.com> Date: Fri, 3 Jul 2026 17:03:57 +1000 Subject: [PATCH] fix(gateway): show lock-hold reason when /compress no-ops --- gateway/slash_commands.py | 13 +++++++++ tests/gateway/test_compress_command.py | 38 ++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/gateway/slash_commands.py b/gateway/slash_commands.py index 4d3951b29705..d4bd8e2ca4dd 100644 --- a/gateway/slash_commands.py +++ b/gateway/slash_commands.py @@ -3563,6 +3563,19 @@ class GatewaySlashCommandsMixin: defer_context_engine_notification=True, ) ) + + # If _compress_context returned unchanged because a + # concurrent compression lock is held, tell the user + # clearly instead of showing the misleading + # "No changes from compression" no-op text. + _lock_skipped = getattr(tmp_agent, "_compression_skipped_due_to_lock", None) + if _lock_skipped: + holder = _lock_skipped if isinstance(_lock_skipped, str) else "unknown" + return ( + f"⏳ Compression already in progress for this session " + f"(holder: {holder}). Please wait for it to finish." + ) + if partial and tail: compressed = rejoin_compressed_head_and_tail(compressed, tail) diff --git a/tests/gateway/test_compress_command.py b/tests/gateway/test_compress_command.py index 708076543f2e..a85589617220 100644 --- a/tests/gateway/test_compress_command.py +++ b/tests/gateway/test_compress_command.py @@ -70,6 +70,7 @@ async def test_compress_command_reports_noop_without_success_banner(): agent_instance.context_compressor.has_content_to_compress.return_value = True agent_instance.session_id = "sess-1" agent_instance._compress_context.return_value = (list(history), "") + agent_instance._compression_skipped_due_to_lock = False def _estimate(messages, **_kwargs): assert messages == history @@ -149,6 +150,7 @@ async def test_compress_command_explains_when_token_estimate_rises(): agent_instance.context_compressor.has_content_to_compress.return_value = True agent_instance.session_id = "sess-1" agent_instance._compress_context.return_value = (compressed, "") + agent_instance._compression_skipped_due_to_lock = False def _estimate(messages, **_kwargs): if messages == history: @@ -199,6 +201,7 @@ async def test_compress_command_appends_warning_when_compression_aborts(): ) agent_instance.session_id = "sess-1" agent_instance._compress_context.return_value = (compressed, "") + agent_instance._compression_skipped_due_to_lock = False def _estimate(messages, **_kwargs): if messages == history: @@ -261,6 +264,7 @@ async def test_compress_command_surfaces_aux_model_failure_even_when_recovered() ) agent_instance.session_id = "sess-1" agent_instance._compress_context.return_value = (compressed, "") + agent_instance._compression_skipped_due_to_lock = False def _estimate(messages, **_kwargs): if messages == history: @@ -319,6 +323,7 @@ async def test_compress_command_passes_session_db_and_persists_rotated_session() return compressed, "" agent_instance._compress_context.side_effect = _compress + agent_instance._compression_skipped_due_to_lock = False def _estimate(messages, **_kwargs): if messages == history: @@ -385,6 +390,7 @@ async def test_compress_command_does_not_repoint_session_when_transcript_write_f return compressed, "" agent_instance._compress_context.side_effect = _compress + agent_instance._compression_skipped_due_to_lock = False def _estimate(messages, **_kwargs): return 100 @@ -440,6 +446,7 @@ async def test_compress_command_in_place_skips_destructive_rewrite(): agent_instance._last_compaction_in_place = True agent_instance.session_id = "sess-1" agent_instance._compress_context.return_value = (compressed, "") + agent_instance._compression_skipped_due_to_lock = False def _estimate(messages, **_kwargs): if messages == history: @@ -482,6 +489,7 @@ async def test_compress_command_preserves_platform_and_gateway_session_key(): agent_instance.context_compressor.has_content_to_compress.return_value = True agent_instance.session_id = "sess-1" agent_instance._compress_context.return_value = (list(history), "") + agent_instance._compression_skipped_due_to_lock = False with ( patch("gateway.run._resolve_runtime_agent_kwargs", return_value={"api_key": "test-key"}), @@ -517,6 +525,7 @@ async def test_compress_command_overrides_stale_resolver_identity(): agent_instance.context_compressor.has_content_to_compress.return_value = True agent_instance.session_id = "sess-1" agent_instance._compress_context.return_value = (list(history), "") + agent_instance._compression_skipped_due_to_lock = False # Resolver injects a WRONG platform and a stale session key. runtime = {"api_key": "test-key", "platform": "discord", "gateway_session_key": "stale-key"} @@ -581,3 +590,32 @@ async def test_compress_command_passes_tool_messages_to_compressor(): # Assistant tool_calls stubs (content=None) must survive too, or the # tool message would dangle without its call. assert any(m.get("tool_calls") for m in passed), "assistant tool_calls stub dropped" + + +@pytest.mark.asyncio +async def test_compress_command_surfaces_lock_skip(): + """When _compress_context skips due to a concurrent lock, the gateway + handler must surface a clear message, not the misleading no-op text.""" + history = _make_history() + runner = _make_runner(history) + agent_instance = MagicMock() + agent_instance.shutdown_memory_provider = MagicMock() + agent_instance.close = MagicMock() + agent_instance._cached_system_prompt = "" + agent_instance.tools = None + agent_instance.context_compressor.has_content_to_compress.return_value = True + agent_instance.session_id = "sess-1" + agent_instance._compress_context.return_value = (list(history), "") + agent_instance._compression_skipped_due_to_lock = "pid=99999" + + with ( + patch("gateway.run._resolve_runtime_agent_kwargs", return_value={"api_key": "***"}), + patch("gateway.run._resolve_gateway_model", return_value="test-model"), + patch("run_agent.AIAgent", return_value=agent_instance), + patch("agent.model_metadata.estimate_request_tokens_rough", return_value=100), + ): + result = await runner._handle_compress_command(_make_event()) + + assert "Compression already in progress" in result + assert "pid=99999" in result + assert "No changes from compression" not in result