From f5ac025714aac956b47feae07fbc75ec399b0fb7 Mon Sep 17 00:00:00 2001 From: jackjin1997 Date: Thu, 16 Apr 2026 19:22:38 +0530 Subject: [PATCH] fix(gateway): guard pending_event.channel_prompt against None in recursive _run_agent Initialize next_channel_prompt before the pending_event check and use getattr with None default, matching the existing pattern for next_source/next_message/next_message_id. Prevents AttributeError when pending_event is None (interrupt path). Cherry-picked from #10953 by @jackjin1997. --- gateway/run.py | 4 ++- tests/gateway/test_pending_event_none.py | 42 ++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 tests/gateway/test_pending_event_none.py diff --git a/gateway/run.py b/gateway/run.py index 7e4cc26a7..68efed918 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -9461,6 +9461,7 @@ class GatewayRunner: next_source = source next_message = pending next_message_id = None + next_channel_prompt = None if pending_event is not None: next_source = getattr(pending_event, "source", None) or source next_message = await self._prepare_inbound_message_text( @@ -9471,6 +9472,7 @@ class GatewayRunner: if next_message is None: return result next_message_id = getattr(pending_event, "message_id", None) + next_channel_prompt = getattr(pending_event, "channel_prompt", None) # Restart typing indicator so the user sees activity while # the follow-up turn runs. The outer _process_message_background @@ -9494,7 +9496,7 @@ class GatewayRunner: session_key=session_key, _interrupt_depth=_interrupt_depth + 1, event_message_id=next_message_id, - channel_prompt=pending_event.channel_prompt, + channel_prompt=next_channel_prompt, ) finally: # Stop progress sender, interrupt monitor, and notification task diff --git a/tests/gateway/test_pending_event_none.py b/tests/gateway/test_pending_event_none.py new file mode 100644 index 000000000..b2e1356fa --- /dev/null +++ b/tests/gateway/test_pending_event_none.py @@ -0,0 +1,42 @@ +"""Tests for the pending_event None guard in recursive _run_agent calls. + +When pending_event is None (Path B: pending comes from interrupt_message), +accessing pending_event.channel_prompt previously raised AttributeError. +This verifies the fix: channel_prompt is captured inside the +`if pending_event is not None:` block and falls back to None otherwise. +""" + +from types import SimpleNamespace + + +def _extract_channel_prompt(pending_event): + """Reproduce the fixed logic from gateway/run.py. + + Mirrors the variable-capture pattern used before the recursive + _run_agent call so we can test both paths without a full runner. + """ + next_channel_prompt = None + if pending_event is not None: + next_channel_prompt = getattr(pending_event, "channel_prompt", None) + return next_channel_prompt + + +class TestPendingEventNoneChannelPrompt: + """Guard against AttributeError when pending_event is None.""" + + def test_none_pending_event_returns_none_channel_prompt(self): + """Path B: pending_event is None — must not raise AttributeError.""" + result = _extract_channel_prompt(None) + assert result is None + + def test_pending_event_with_channel_prompt_passes_through(self): + """Path A: pending_event present — channel_prompt is forwarded.""" + event = SimpleNamespace(channel_prompt="You are a helpful bot.") + result = _extract_channel_prompt(event) + assert result == "You are a helpful bot." + + def test_pending_event_without_channel_prompt_returns_none(self): + """Path A: pending_event present but has no channel_prompt attribute.""" + event = SimpleNamespace() + result = _extract_channel_prompt(event) + assert result is None