From 5d349ea857d43b33fdb94bb8ab1d74c9979bf195 Mon Sep 17 00:00:00 2001 From: Erosika Date: Fri, 24 Apr 2026 18:27:34 -0400 Subject: [PATCH] fix(honcho): hold RLock across new_session's get_or_create to close race new_session() was popping the old cached session, releasing the lock, calling get_or_create, then re-acquiring the lock to insert. A concurrent caller could observe the empty-cache window and race-create its own session, producing two divergent session objects for the same key. _cache_lock is an RLock, so nested reacquisition inside get_or_create is safe. Hold it across the whole pop/create/insert sequence. Follow-up to #13510 (@hekaru-agent). --- plugins/memory/honcho/session.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/memory/honcho/session.py b/plugins/memory/honcho/session.py index 8e7018d4..46eb3118 100644 --- a/plugins/memory/honcho/session.py +++ b/plugins/memory/honcho/session.py @@ -493,6 +493,10 @@ class HonchoSessionManager: """ import time + # Hold the reentrant lock across get_or_create so a concurrent caller + # can't observe the (old-popped, new-not-yet-inserted) gap and create + # its own session under the raw key. `_cache_lock` is an RLock so + # nested reacquisition inside get_or_create is safe. with self._cache_lock: # Remove old session from caches (but don't delete from Honcho) old_session = self._cache.pop(key, None) @@ -503,11 +507,10 @@ class HonchoSessionManager: timestamp = int(time.time()) new_key = f"{key}:{timestamp}" - # get_or_create will create a fresh session - session = self.get_or_create(new_key) + # get_or_create will create a fresh session + session = self.get_or_create(new_key) - # Cache under the original key so callers find it by the expected name - with self._cache_lock: + # Cache under the original key so callers find it by the expected name self._cache[key] = session logger.info("Created new session for %s (honcho: %s)", key, session.honcho_session_id)