From d3a79ee6dbf4b78e64d2154c9207df675fd32ff2 Mon Sep 17 00:00:00 2001 From: Arsh Verma Date: Wed, 16 Sep 2026 00:05:41 +0530 Subject: [PATCH] fix: acquire _sessions_lock in TCP notification handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The notification handler inside _connect_via_tcp was reading self._sessions.get(session_id) without holding _sessions_lock, while the identical handler in _connect_via_stdio correctly wraps the lookup in 'with self._sessions_lock:'. _sessions is mutated from the event loop thread during session create/resume/destroy and read here from the notification path scheduled via call_soon_threadsafe. Every other access (15+ sites) uses the lock — this was the only one that didn't. Without the lock, a notification arriving during session registration can see an inconsistent dict state, silently drop the event, and leave the session hanging forever (permission requests, tool calls, and MCP OAuth all flow through _dispatch_event). On free-threaded Python 3.13+ (PEP 703), the unprotected read becomes a hard data race on dict internals. --- python/copilot/client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/copilot/client.py b/python/copilot/client.py index d8d2f7e3f4..f3b0923865 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -4746,7 +4746,8 @@ def handle_notification(method: str, params: dict): event_dict = params["event"] # Convert dict to SessionEvent object event = session_event_from_dict(event_dict) - session = self._sessions.get(session_id) + with self._sessions_lock: + session = self._sessions.get(session_id) if session: session._dispatch_event(event) elif method == "session.lifecycle":