fix(runners): honor before_run_callback early-exit for Workflow runs#6032
fix(runners): honor before_run_callback early-exit for Workflow runs#6032garyzava wants to merge 4 commits into
Conversation
In the Workflow run path (_run_node_async), the before_run plugin callback was awaited but its return value was discarded, so a callback returning a Content (the documented signal to halt the run) was ignored and execution continued into the workflow. Capture the callback result and, when it is a Content, emit it as the early-exit event and stop — matching the existing behavior of the non-workflow run path. Adds a regression test: a plugin whose before_run_callback returns Content halts the workflow and the node never executes. Fixes google#6013
🔍 ADK Pull Request Analysis: PR #6032Title: fix(runners): honor before_run_callback early-exit for Workflow runs Executive Summary
Detailed Findings & Analysis1. Objectives & Impact ("What does it do?")
2. Justification & Value ("Is it a valid and useful change?")
3. Principle & Style Alignment Checklist ("Does it follow rules?")
Summary of Triage & Findings
|
|
Hi @garyzava , Thank you for your contribution! We appreciate you taking the time to submit this pull request. Your PR has been received by the team and is currently under review. Can you meanwhile address the adk bot nits suggestions. |
|
Hi @GWeale , can you please review this. |
|
Hi @rohityan, thanks for taking a look! The bot's nit (missing type annotations on the |
Summary
In the Workflow run path (
Runner._run_node_async), thebefore_runplugincallback was awaited but its return value was discarded. Per the plugin contract,
returning a
Contentfrombefore_run_callbackshould halt the run and end itwith that content — but for Workflow root agents the value was ignored and
execution continued into the workflow.
This captures the callback result and, when it is a
Content, emits it as theearly-exit event and stops — matching the existing behavior of the non-workflow
run path (
run_async).Fixes #6013
What changed
src/google/adk/runners.py— in_run_node_async, capturerun_before_run_callback(...)'s result; if it's atypes.Content, build theearly-exit
Event, append it (subject to_should_append_event), yield it, andreturn — instead of unconditionally proceeding. Mirrors the non-workflow path.
tests/unittests/workflow/test_workflow_failures.py— regression test: a pluginwhose
before_run_callbackreturnsContenthalts the workflow and the nodenever executes.
Why this approach
The non-workflow path already implements the documented early-exit contract
(capture the result, emit an early-exit event, skip execution). This change makes
the Workflow path consistent with it rather than introducing new behavior, so the
fix is minimal and the intended semantics are unambiguous.
How I tested
The new test (
test_workflow_halts_when_before_run_callback_returns_content)fails before the change (the node executes; no early-exit event) and passes
after. Existing workflow-failure tests and the non-workflow plugin tests
(
tests/unittests/test_runners.py -k plugin) still pass.pyinkandisortreport no changes.
Backward compatibility
No behavior change when
before_run_callbackreturnsNone(the common case) —the workflow runs exactly as before. Only the documented halt-on-
Contentcase isaffected, and it now matches the non-workflow path.