Skip to content

Code execution continuation is misclassified as MODEL_RETURNED_NO_CONTENT #7002

Description

@saransh-translucent

Describe the bug

In ADK 2.7.0, 2.8.0, and current main, a valid non-built-in code-execution response that ends with finish_reason=STOP executes successfully but the LLM loop terminates with MODEL_RETURNED_NO_CONTENT before the model can consume the execution result and produce a final answer.

This differs from #5631. In that issue the provider's post-tool model call genuinely returns an empty response. Here the provider returned executable code; ADK itself clears that response's content as its internal signal to continue after executing the code.

Minimal reproduction

Use a deterministic BaseLlm that returns these two responses:

responses = [
    LlmResponse(
        content=types.Content(
            role="model",
            parts=[types.Part(text="```python\nprint(6 * 7)\n```")],
        ),
        finish_reason=types.FinishReason.STOP,
    ),
    LlmResponse(
        content=types.Content(
            role="model",
            parts=[types.Part(text="The answer is 42.")],
        ),
        finish_reason=types.FinishReason.STOP,
    ),
]

agent = Agent(
    name="code_agent",
    model=sequence_model,
    code_executor=recording_code_executor,
)
events = testing_utils.InMemoryRunner(agent).run("What is 6 * 7?")

The recording executor can simply return:

CodeExecutionResult(stdout="42\n")

Observed behavior

  • the model is called once
  • the code executor runs successfully
  • ADK emits the code and execution-result events
  • ADK then emits MODEL_RETURNED_NO_CONTENT
  • the second configured model response is never requested

Expected behavior

  • the model is called twice
  • the second request includes the execution result
  • the final answer is emitted
  • a genuinely provider-empty non-streaming STOP response still emits MODEL_RETURNED_NO_CONTENT

Root cause

BaseLlmFlow._postprocess_async currently runs response processors before validating a genuinely empty non-streaming STOP response.

The code-execution response processor intentionally sets llm_response.content = None after execution so the outer LLM loop continues. The subsequent empty-STOP guard mistakes that internal mutation for a provider-empty response and assigns MODEL_RETURNED_NO_CONTENT, preventing the continuation.

Moving the genuine-empty check before response processors preserves both contracts:

  1. provider-empty STOP responses are still surfaced as errors
  2. processor-cleared code-execution responses remain the continuation sentinel

Environment

  • google-adk: reproduced with 2.7.0 and 2.8.0; code path is present on main
  • Python: 3.14
  • executor: non-built-in BaseCodeExecutor

I have a small regression-tested patch prepared and can open it once this issue is filed.

Activity

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

Metadata

Metadata

Labels

core[Component] This issue is related to the core interface and implementation

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions