Repository navigation
Description
🔴 Required Information
Describe the Bug:
With code_executor=BuiltInCodeExecutor(), every model reply is preceded by an empty event. That event has no content and no actions, but is_final_response() returns True for it, and it's stored in the session.
The built-in branch of the code execution response processor (flows/llm_flows/extensions/_code_execution.py) always yields an Event(actions=event_actions). It does so even when the response had no image to save as an artifact, in which case event_actions is empty.
is_final_response() documents that "Application and UI layers can rely on this helper to detect a complete, user-facing response". So a run loop that stops at the first final response returns nothing. This is what #4138 ran into: "only get a empty data event when running in my custom runner". The reporter closed it by no longer stopping at is_final_response(), but the empty event is still emitted on main.
Steps to Reproduce:
- Create an
LlmAgentwithcode_executor=BuiltInCodeExecutor(). - Have the model answer with plain text, so there's no image.
- Iterate
runner.run_async(...)and print each event's content andis_final_response().
Expected Behavior:
Only the model's reply is emitted. An extra event appears only when it carries an artifact_delta for a saved image.
Observed Behavior:
'a' content= None actions= {'state_delta': {}} final= True
'a' content= parts=[Part(text='The answer is 42.')] ... final= True
session events: 3 [('user', True), ('a', False), ('a', True)]
Environment Details:
- ADK Library Version (pip show google-adk): 2.13.0 (also on
mainat e9e8f61) - Desktop OS: macOS
- Python Version (python -V): 3.11.16
Model Information:
- Are you using LiteLLM: No
- Which model is being used: Gemini 2.x / 3 (
BuiltInCodeExecutorrequires a Gemini model); the repro uses a mockBaseLlm
🟡 Optional Information
Minimal Reproduction Code:
agent = LlmAgent(name="a", model=model, code_executor=BuiltInCodeExecutor())
runner = InMemoryRunner(agent=agent)
session = await runner.session_service.create_session(app_name=runner.app_name, user_id="u")
async for event in runner.run_async(
user_id="u", session_id=session.id,
new_message=types.Content(role="user", parts=[types.Part(text="What is 6 * 7?")])):
print(repr(event.author), "content=", event.content, "final=", event.is_final_response())
if event.is_final_response():
break # stops on the empty event, before the answerHow often has this issue occurred?:
- Always (100%), for every reply that contains no image
Activity
sanketpatil06 commentedon Nov 3, 2026
Thanks for the report and fix, @adityasingh2400. we confirmed this on
mainand checked #7879 locally. Text-only replies no longer get the empty final event, and image artifacts are still saved. The regression test fails onmainand passes with the fix, and no other tests broke.The PR is currently under review and will get merged once approved.