Skip to content

RemoteA2aAgent in task mode hangs Runner.run_async() when the caller stops at its error event #7918

Description

@hyeonsang010716

🔴 Required Information

Describe the Bug:
In task mode, RemoteA2aAgent._run_async_impl yields the finish_task and agent state events from its finally block. Most failure paths (authentication or initialization failure, no parts to send, a request interceptor returning an event, an unfinished stream, a request error) set should_release_task_control before they yield the error event. If the generator is closed while it is paused at that error event, the finally block yields again and Python raises RuntimeError: async generator ignored GeneratorExit.

This happens when the caller stops reading Runner.run_async() at that event, with break or by cancelling the task. The Runner then cancels the root node, and the cancellation closes the agent's generator. The RuntimeError replaces the cancellation:

  • NodeRunner handles it as a node failure. It logs Node execution failed with exception with the traceback below, and on_agent_error_callback plugins receive the RuntimeError.
  • NodeRunner then enqueues an error event for that failure. Nothing reads the event queue anymore, so the remote agent's node task waits forever. Closing Runner.run_async() never finishes, and the caller's break or cancel() hangs.

With abort_signal passed to run_async(), the run still closes, but the log and the plugin callback happen the same way.

Steps to Reproduce:

  1. Run the code below. Nothing listens on 127.0.0.1:9, so the remote agent fails to initialize.

Expected Behavior:
The run closes after break, and no agent error is reported.

stopping at: Failed to initialize remote A2A agent: ...
closed

Observed Behavior:
closed is never printed and the script does not exit. Before it hangs, this is logged:

Node execution failed with exception
Traceback (most recent call last):
  File ".../google/adk/agents/base_agent.py", line 448, in _run
    yield event
GeneratorExit

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File ".../google/adk/workflow/_node_runner.py", line 170, in run
    await self._execute_node(ctx, node_input)
  ...
  File ".../google/adk/agents/base_agent.py", line 446, in _run
    async with Aclosing(impl_fn(ctx)) as agen:
  ...
RuntimeError: async generator ignored GeneratorExit

The plugin also prints on_agent_error_callback: RuntimeError('async generator ignored GeneratorExit').

Environment Details:

  • ADK Library Version (pip show google-adk): 2.13.0 (also on main at 9a5117b)
  • Desktop OS: macOS
  • Python Version (python -V): 3.14.7

Model Information:

  • Are you using LiteLLM: No
  • Which model is being used: N/A (the repro uses a stub model)

🟡 Optional Information

Additional Context:
Calling _run_async_impl directly shows the cause: after the first (error) event, aclose() raises the RuntimeError in task mode and returns normally when mode is not set.

Only the paths that set should_release_task_control before yielding are affected. A close while the response stream is being read, or after finish_task is received, is fine.

Minimal Reproduction Code:

import asyncio

from google.adk.agents import LlmAgent
from google.adk.agents.remote_a2a_agent import RemoteA2aAgent
from google.adk.apps import App
from google.adk.models import BaseLlm
from google.adk.models import LlmResponse
from google.adk.plugins import BasePlugin
from google.adk.runners import InMemoryRunner
from google.adk.utils.context_utils import Aclosing
from google.genai import types


class CallRemote(BaseLlm):

  async def generate_content_async(self, llm_request, stream=False):
    yield LlmResponse(
        content=types.Content(
            role="model",
            parts=[types.Part.from_function_call(name="remote", args={})],
        )
    )


class PrintAgentErrors(BasePlugin):

  async def on_agent_error_callback(self, *, agent, callback_context, error):
    print("on_agent_error_callback:", repr(error))


async def main():
  remote = RemoteA2aAgent(
      name="remote",
      description="Remote agent",
      agent_card="http://127.0.0.1:9/.well-known/agent-card.json",
      mode="task",
  )
  root = LlmAgent(
      name="root", model=CallRemote(model="call-remote"), sub_agents=[remote]
  )
  runner = InMemoryRunner(
      app=App(
          name="app",
          root_agent=root,
          plugins=[PrintAgentErrors(name="print_agent_errors")],
      )
  )
  session = await runner.session_service.create_session(
      app_name="app", user_id="user"
  )

  async with Aclosing(
      runner.run_async(
          user_id="user",
          session_id=session.id,
          new_message=types.Content(
              role="user", parts=[types.Part(text="start")]
          ),
      )
  ) as events:
    async for event in events:
      if event.author == "remote" and event.error_message:
        print("stopping at:", event.error_message)
        break
  print("closed")


asyncio.run(main())

How often has this issue occurred?:

  • Always (100%)

Activity

  1. added theissue type on Nov 5, 2026
  2. added
    a2a[Component] This issue is related a2a support inside ADK.
    on Nov 5, 2026
  3. llalitkumarrr commented on Nov 5, 2026

    @llalitkumarrr
    Collaborator

    Hello @hyeonsang010716,

    Thank you for raising this issue and for your contribution. Your PR is currently under review and will be merged once the changes are approved, we appreciate your patience in the meantime.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

a2a[Component] This issue is related a2a support inside ADK.

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions