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
6 changes: 6 additions & 0 deletions gateway/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -14915,6 +14915,12 @@ def _sweep_idle_cached_agents(self) -> int:
"Agent cache idle-TTL evict: session=%s (idle=%.0fs)",
key, now - getattr(agent, "_last_activity_ts", now),
)
# Remove from sessions.json so the stale entry doesn't route
# messages into an ended session until gateway restart.
try:
self.session_store.remove(key)
except Exception:
logger.debug("Failed to remove stale session entry for %s", key, exc_info=True)
threading.Thread(
target=self._release_evicted_agent_soft,
args=(agent,),
Expand Down
16 changes: 16 additions & 0 deletions gateway/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -1615,6 +1615,22 @@ def switch_session(self, session_key: str, target_session_id: str) -> Optional[S

return new_entry

def remove(self, session_key: str) -> bool:
"""Remove a session key from the routing index.

Called after idle-TTL eviction to prevent sessions.json from holding
stale entries that route messages into ended sessions.

Returns True if the key was present and removed, False otherwise.
"""
with self._lock:
self._ensure_loaded_locked()
if session_key not in self._entries:
return False
del self._entries[session_key]
self._save()
return True

def list_sessions(self, active_minutes: Optional[int] = None) -> List[SessionEntry]:
"""List all sessions, optionally filtered by activity."""
with self._lock:
Expand Down
34 changes: 34 additions & 0 deletions tests/gateway/test_session_store_stale_prune.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,37 @@ def test_live_entry_survives_load(self, tmp_path):
store._ensure_loaded()

assert "active_key" in store._entries


def test_session_store_remove():
"""SessionStore.remove() should remove a key from the routing index.

Regression test for #54878: after idle-TTL eviction, sessions.json
held stale entries that routed messages into ended sessions.
"""
import tempfile
from pathlib import Path
from gateway.session import SessionStore, SessionSource
from gateway.config import GatewayConfig, Platform

with tempfile.TemporaryDirectory() as tmp:
sessions_dir = Path(tmp) / "sessions"
config = GatewayConfig()
store = SessionStore(sessions_dir, config)

# Create a session entry via get_or_create
source = SessionSource(
platform=Platform.TELEGRAM,
chat_id="12345",
user_id="user1",
)
entry = store.get_or_create_session(source)
assert entry is not None

# Remove the key
removed = store.remove(entry.session_key)
assert removed is True

# Removing again should return False
removed2 = store.remove(entry.session_key)
assert removed2 is False