diff --git a/src/a2a/server/agent_execution/active_task.py b/src/a2a/server/agent_execution/active_task.py index 5e3dab50f..6a9f27ed3 100644 --- a/src/a2a/server/agent_execution/active_task.py +++ b/src/a2a/server/agent_execution/active_task.py @@ -1003,6 +1003,11 @@ async def _mark_task_as_failed(self, exception: Exception) -> Task | None: pass return task + @property + def task_created(self) -> bool: + """Whether a task exists, so ``get_task()`` will not block.""" + return self._task_created.is_set() + async def get_task(self) -> Task: """Get task from db.""" await self._task_created.wait() diff --git a/src/a2a/server/request_handlers/default_request_handler_v2.py b/src/a2a/server/request_handlers/default_request_handler_v2.py index b135f529f..90716240a 100644 --- a/src/a2a/server/request_handlers/default_request_handler_v2.py +++ b/src/a2a/server/request_handlers/default_request_handler_v2.py @@ -54,6 +54,7 @@ from a2a.utils.errors import ( ExtendedAgentCardNotConfiguredError, InternalError, + InvalidAgentResponseError, InvalidParamsError, PushNotificationNotSupportedError, TaskNotCancelableError, @@ -444,6 +445,11 @@ async def on_message_send( # noqa: D102 # pushed after a Message. if result is None: + if not active_task.task_created: + # A silent executor means get_task() would block forever. + raise InvalidAgentResponseError( + message='Agent execution produced no events' + ) logger.debug('Missing result for task %s', request_context.task_id) result = await active_task.get_task() diff --git a/tests/server/request_handlers/test_default_request_handler_v2.py b/tests/server/request_handlers/test_default_request_handler_v2.py index b30fdc695..b010b71b6 100644 --- a/tests/server/request_handlers/test_default_request_handler_v2.py +++ b/tests/server/request_handlers/test_default_request_handler_v2.py @@ -2776,3 +2776,38 @@ async def test_on_message_send_stream_message_response_releases_active_task() -> assert [type(event) for event in events] == [Message] assert await _wait_until_released(handler, baseline) == set() assert handler._active_task_registry._active_tasks == {} + + +class SilentExecutor(AgentExecutor): + """Returns normally without producing any event (issue #1330).""" + + async def execute(self, context: RequestContext, event_queue: EventQueue): + return None + + async def cancel(self, context: RequestContext, event_queue: EventQueue): + pass + + +@pytest.mark.asyncio +async def test_on_message_send_raises_when_agent_produces_no_events(): + """A silent executor must fail fast instead of hanging forever (#1330).""" + request_handler = DefaultRequestHandlerV2( + agent_executor=SilentExecutor(), + task_store=InMemoryTaskStore(), + agent_card=create_default_agent_card(), + ) + params = SendMessageRequest( + message=Message( + role=Role.ROLE_USER, + message_id='msg_silent', + parts=[Part(text='Hi')], + ), + configuration=SendMessageConfiguration( + accepted_output_modes=['text/plain'] + ), + ) + + with pytest.raises(InvalidAgentResponseError, match='no events'): + await request_handler.on_message_send( + params, create_server_call_context() + )