From 014f926e8f2c611031a952a21046f8b2a37a30ae Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 19:28:12 +0000 Subject: [PATCH 1/6] Initial plan From c4f59441f82f89b176c3d726daf75c4d2f93fbe4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 19:36:33 +0000 Subject: [PATCH 2/6] Add graceful error handling for unknown/malformed session events in Python SDK Co-authored-by: friggeri <106686+friggeri@users.noreply.github.com> --- python/copilot/client.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/python/copilot/client.py b/python/copilot/client.py index 0828e6e..fcab866 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -726,7 +726,11 @@ def handle_notification(method: str, params: dict): session_id = params["sessionId"] event_dict = params["event"] # Convert dict to SessionEvent object - event = session_event_from_dict(event_dict) + try: + event = session_event_from_dict(event_dict) + except Exception: + # Silently ignore unknown/malformed event types for forward compatibility + return with self._sessions_lock: session = self._sessions.get(session_id) if session: @@ -801,7 +805,11 @@ def handle_notification(method: str, params: dict): session_id = params["sessionId"] event_dict = params["event"] # Convert dict to SessionEvent object - event = session_event_from_dict(event_dict) + try: + event = session_event_from_dict(event_dict) + except Exception: + # Silently ignore unknown/malformed event types for forward compatibility + return session = self._sessions.get(session_id) if session: session._dispatch_event(event) From 32d554eadc63eedd58c23f1548d4fb42fd549729 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 19:38:08 +0000 Subject: [PATCH 3/6] Add unit tests for unknown/malformed session event handling Co-authored-by: friggeri <106686+friggeri@users.noreply.github.com> --- python/test_unknown_events.py | 121 ++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 python/test_unknown_events.py diff --git a/python/test_unknown_events.py b/python/test_unknown_events.py new file mode 100644 index 0000000..887cc62 --- /dev/null +++ b/python/test_unknown_events.py @@ -0,0 +1,121 @@ +""" +Test that unknown/malformed session events are handled gracefully. +""" + +from datetime import datetime +from uuid import uuid4 + +import pytest + +from copilot import CopilotClient + + +class TestUnknownEventHandling: + """Test graceful handling of unknown and malformed session events.""" + + def test_event_parsing_with_unknown_type(self): + """Verify that unknown event types map to UNKNOWN enum value.""" + from copilot.generated.session_events import SessionEventType, session_event_from_dict + + unknown_event = { + "id": str(uuid4()), + "timestamp": datetime.now().isoformat(), + "parentId": None, + "type": "session.completely_new_event_from_future", + "data": {}, + } + + event = session_event_from_dict(unknown_event) + assert event.type == SessionEventType.UNKNOWN, \ + f"Expected UNKNOWN, got {event.type}" + + def test_malformed_data_raises_exception(self): + """Malformed data should raise exceptions (caught by handler).""" + from copilot.generated.session_events import session_event_from_dict + + # Bad UUID format + malformed_event = { + "id": "not-a-uuid", + "timestamp": datetime.now().isoformat(), + "parentId": None, + "type": "session.start", + "data": {}, + } + + with pytest.raises((ValueError, AssertionError)): + session_event_from_dict(malformed_event) + + # Bad timestamp format + malformed_event2 = { + "id": str(uuid4()), + "timestamp": "invalid-timestamp", + "parentId": None, + "type": "session.start", + "data": {}, + } + + with pytest.raises(Exception): + session_event_from_dict(malformed_event2) + + def test_handler_catches_parsing_exceptions(self): + """The notification handler should catch and ignore parsing exceptions.""" + from copilot.generated.session_events import session_event_from_dict + + events_dispatched = [] + + def mock_dispatch(event): + events_dispatched.append(event) + + # Test 1: Known event should work + known_event = { + "id": str(uuid4()), + "timestamp": datetime.now().isoformat(), + "parentId": None, + "type": "session.start", + "data": {}, + } + + try: + event = session_event_from_dict(known_event) + mock_dispatch(event) + except Exception as e: + pytest.fail(f"Known event should not raise: {e}") + + assert len(events_dispatched) == 1, "Known event should be dispatched" + + # Test 2: Unknown event type should use UNKNOWN enum + unknown_event = { + "id": str(uuid4()), + "timestamp": datetime.now().isoformat(), + "parentId": None, + "type": "session.future_unknown_event", + "data": {}, + } + + try: + event = session_event_from_dict(unknown_event) + mock_dispatch(event) + except Exception as e: + pytest.fail(f"Unknown event should not raise: {e}") + + assert len(events_dispatched) == 2, "Unknown event should be dispatched with UNKNOWN type" + + # Test 3: Malformed event - simulate what the handler does + malformed_event = { + "id": "not-a-valid-uuid", + "timestamp": datetime.now().isoformat(), + "parentId": None, + "type": "session.start", + "data": {}, + } + + # This simulates the try-except in the notification handler + try: + event = session_event_from_dict(malformed_event) + mock_dispatch(event) + except Exception: + # Handler catches and returns, event not dispatched + pass + + assert len(events_dispatched) == 2, "Malformed event should not be dispatched" + From 5b1c1752d5f81ce8d87d7323ecbb66992a231b57 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 19:40:06 +0000 Subject: [PATCH 4/6] Improve test specificity for exception types Co-authored-by: friggeri <106686+friggeri@users.noreply.github.com> --- python/test_unknown_events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/test_unknown_events.py b/python/test_unknown_events.py index 887cc62..93b20bf 100644 --- a/python/test_unknown_events.py +++ b/python/test_unknown_events.py @@ -54,7 +54,7 @@ def test_malformed_data_raises_exception(self): "data": {}, } - with pytest.raises(Exception): + with pytest.raises((ValueError, TypeError, OSError)): session_event_from_dict(malformed_event2) def test_handler_catches_parsing_exceptions(self): From 39d47dcba2810481f1198435d3c1c4a6f88de7b1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 Jan 2026 13:27:22 +0000 Subject: [PATCH 5/6] Revert broad error suppression - let parsing errors surface for visibility Co-authored-by: SteveSandersonMS <1101362+SteveSandersonMS@users.noreply.github.com> --- python/copilot/client.py | 12 +- python/test_event_forward_compatibility.py | 65 +++++++++++ python/test_unknown_events.py | 121 --------------------- 3 files changed, 67 insertions(+), 131 deletions(-) create mode 100644 python/test_event_forward_compatibility.py delete mode 100644 python/test_unknown_events.py diff --git a/python/copilot/client.py b/python/copilot/client.py index fcab866..0828e6e 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -726,11 +726,7 @@ def handle_notification(method: str, params: dict): session_id = params["sessionId"] event_dict = params["event"] # Convert dict to SessionEvent object - try: - event = session_event_from_dict(event_dict) - except Exception: - # Silently ignore unknown/malformed event types for forward compatibility - return + event = session_event_from_dict(event_dict) with self._sessions_lock: session = self._sessions.get(session_id) if session: @@ -805,11 +801,7 @@ def handle_notification(method: str, params: dict): session_id = params["sessionId"] event_dict = params["event"] # Convert dict to SessionEvent object - try: - event = session_event_from_dict(event_dict) - except Exception: - # Silently ignore unknown/malformed event types for forward compatibility - return + event = session_event_from_dict(event_dict) session = self._sessions.get(session_id) if session: session._dispatch_event(event) diff --git a/python/test_event_forward_compatibility.py b/python/test_event_forward_compatibility.py new file mode 100644 index 0000000..2449cd4 --- /dev/null +++ b/python/test_event_forward_compatibility.py @@ -0,0 +1,65 @@ +""" +Test that unknown event types are handled gracefully for forward compatibility. + +This test verifies that: +1. The session.usage_info event type is recognized +2. Unknown future event types map to UNKNOWN enum value +3. Real parsing errors (malformed data) are NOT suppressed and surface for visibility +""" + +from datetime import datetime +from uuid import uuid4 + +import pytest + +from copilot.generated.session_events import SessionEventType, session_event_from_dict + + +class TestEventForwardCompatibility: + """Test forward compatibility for unknown event types.""" + + def test_session_usage_info_is_recognized(self): + """The session.usage_info event type should be in the enum.""" + assert SessionEventType.SESSION_USAGE_INFO.value == "session.usage_info" + + def test_unknown_event_type_maps_to_unknown(self): + """Unknown event types should map to UNKNOWN enum value for forward compatibility.""" + unknown_event = { + "id": str(uuid4()), + "timestamp": datetime.now().isoformat(), + "parentId": None, + "type": "session.future_feature_from_server", + "data": {}, + } + + event = session_event_from_dict(unknown_event) + assert event.type == SessionEventType.UNKNOWN, \ + f"Expected UNKNOWN, got {event.type}" + + def test_malformed_uuid_raises_error(self): + """Malformed UUIDs should raise ValueError for visibility, not be suppressed.""" + malformed_event = { + "id": "not-a-valid-uuid", + "timestamp": datetime.now().isoformat(), + "parentId": None, + "type": "session.start", + "data": {}, + } + + # This should raise an error and NOT be silently suppressed + with pytest.raises(ValueError): + session_event_from_dict(malformed_event) + + def test_malformed_timestamp_raises_error(self): + """Malformed timestamps should raise an error for visibility.""" + malformed_event = { + "id": str(uuid4()), + "timestamp": "not-a-valid-timestamp", + "parentId": None, + "type": "session.start", + "data": {}, + } + + # This should raise an error and NOT be silently suppressed + with pytest.raises(Exception): + session_event_from_dict(malformed_event) diff --git a/python/test_unknown_events.py b/python/test_unknown_events.py deleted file mode 100644 index 93b20bf..0000000 --- a/python/test_unknown_events.py +++ /dev/null @@ -1,121 +0,0 @@ -""" -Test that unknown/malformed session events are handled gracefully. -""" - -from datetime import datetime -from uuid import uuid4 - -import pytest - -from copilot import CopilotClient - - -class TestUnknownEventHandling: - """Test graceful handling of unknown and malformed session events.""" - - def test_event_parsing_with_unknown_type(self): - """Verify that unknown event types map to UNKNOWN enum value.""" - from copilot.generated.session_events import SessionEventType, session_event_from_dict - - unknown_event = { - "id": str(uuid4()), - "timestamp": datetime.now().isoformat(), - "parentId": None, - "type": "session.completely_new_event_from_future", - "data": {}, - } - - event = session_event_from_dict(unknown_event) - assert event.type == SessionEventType.UNKNOWN, \ - f"Expected UNKNOWN, got {event.type}" - - def test_malformed_data_raises_exception(self): - """Malformed data should raise exceptions (caught by handler).""" - from copilot.generated.session_events import session_event_from_dict - - # Bad UUID format - malformed_event = { - "id": "not-a-uuid", - "timestamp": datetime.now().isoformat(), - "parentId": None, - "type": "session.start", - "data": {}, - } - - with pytest.raises((ValueError, AssertionError)): - session_event_from_dict(malformed_event) - - # Bad timestamp format - malformed_event2 = { - "id": str(uuid4()), - "timestamp": "invalid-timestamp", - "parentId": None, - "type": "session.start", - "data": {}, - } - - with pytest.raises((ValueError, TypeError, OSError)): - session_event_from_dict(malformed_event2) - - def test_handler_catches_parsing_exceptions(self): - """The notification handler should catch and ignore parsing exceptions.""" - from copilot.generated.session_events import session_event_from_dict - - events_dispatched = [] - - def mock_dispatch(event): - events_dispatched.append(event) - - # Test 1: Known event should work - known_event = { - "id": str(uuid4()), - "timestamp": datetime.now().isoformat(), - "parentId": None, - "type": "session.start", - "data": {}, - } - - try: - event = session_event_from_dict(known_event) - mock_dispatch(event) - except Exception as e: - pytest.fail(f"Known event should not raise: {e}") - - assert len(events_dispatched) == 1, "Known event should be dispatched" - - # Test 2: Unknown event type should use UNKNOWN enum - unknown_event = { - "id": str(uuid4()), - "timestamp": datetime.now().isoformat(), - "parentId": None, - "type": "session.future_unknown_event", - "data": {}, - } - - try: - event = session_event_from_dict(unknown_event) - mock_dispatch(event) - except Exception as e: - pytest.fail(f"Unknown event should not raise: {e}") - - assert len(events_dispatched) == 2, "Unknown event should be dispatched with UNKNOWN type" - - # Test 3: Malformed event - simulate what the handler does - malformed_event = { - "id": "not-a-valid-uuid", - "timestamp": datetime.now().isoformat(), - "parentId": None, - "type": "session.start", - "data": {}, - } - - # This simulates the try-except in the notification handler - try: - event = session_event_from_dict(malformed_event) - mock_dispatch(event) - except Exception: - # Handler catches and returns, event not dispatched - pass - - assert len(events_dispatched) == 2, "Malformed event should not be dispatched" - From e1cc9a710cc4f8ce03409a763ed5b2246e47e21a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 Jan 2026 13:29:14 +0000 Subject: [PATCH 6/6] Use specific exception types in test instead of broad Exception Co-authored-by: SteveSandersonMS <1101362+SteveSandersonMS@users.noreply.github.com> --- python/test_event_forward_compatibility.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/test_event_forward_compatibility.py b/python/test_event_forward_compatibility.py index 2449cd4..6e42788 100644 --- a/python/test_event_forward_compatibility.py +++ b/python/test_event_forward_compatibility.py @@ -61,5 +61,5 @@ def test_malformed_timestamp_raises_error(self): } # This should raise an error and NOT be silently suppressed - with pytest.raises(Exception): + with pytest.raises((ValueError, TypeError)): session_event_from_dict(malformed_event)