Repository navigation
ADK 2.0 ignores before_run callback for Workflow #6013
Description
Activity
- addedworkflow[Component] This issue is related to ADKworkflow[Component] This issue is related to ADKworkflow
on Jun 8, 2026 I will begin by reading the instructions for the adk-issue-analyze skill to understand the strict read-only analysis process.I will fetch the details of GitHub issue #6013.I will search for the path of
runner.pyusingfind_by_name.I will search for the stringrun_before_run_callbackto locate where this callback is invoked.I will viewsrc/google/adk/runners.pystarting around line 500 to check howrun_before_run_callbackis called.I will viewsrc/google/adk/runners.pyaround line 1350 to see the second usage ofrun_before_run_callback.I will viewsrc/google/adk/runners.pyfrom line 440 to 500 to identify the surrounding class and method.I will search for the class definitions inrunners.pyto see which class contains_run_node_async.I will search for_run_node_asynccalls inrunners.py.I will view lines 580 to 650 ofsrc/google/adk/runners.py.I will viewsrc/google/adk/runners.pyfrom line 640 to 700 to check for any usage ofrun_before_run_callbackin_run_node_live.I will run aghcommand to search for any pull requests associated with issue #6013.I will search for test files related to plugins or runners to see how they are tested.I will search for callback or plugin test instances intests/unittests/test_runners.py.I will viewtests/unittests/test_runners.pyfrom line 145 to 180 to inspect the test plugin setup.I will view lines 940 to 960 intests/unittests/test_runners.py.I will view thetests/unittests/runners/test_runner_node.pyfile to see how node or workflow runners are tested.I will viewsrc/google/adk/plugins/plugin_manager.pyaround line 145 to see howrun_before_run_callbackhandles returning values.I will run the runner node unit tests to confirm the test suite is functioning correctly.# GitHub Issue #6013 Analysis: ADK 2.0 ignores before_run callback for WorkflowExecutive Summary
- What is broken? When executing a workflow (or any other
BaseNode/ agent node) viaRunner(node=...), ADK 2.0 executes_run_node_asyncwhere it discards the return value ofbefore_run_callback. Unlike the standard LLM sequential execution path in_exec_with_plugin, early exit returned values of typetypes.Contentare swallowed and never checked, letting node/workflow execution progress unchecked. - Is there a linked PR that fixes this issue? None
- Recommendation: Should Fix (High Priority) - Update
_run_node_asyncto capture the callback return value and gracefully early-exit if atypes.Contentinstance is returned, mirroring the behavior of_exec_with_plugin.
Detailed Analysis
1. Root Cause Analysis ("What is broken?")
-
The Bug:
In runners.py, agent workflows are executed through_run_node_asyncbecauseWorkflowis a subclass ofBaseNode. At line 532, the before-run callbacks are executed:# Run before_run callbacks await ic.plugin_manager.run_before_run_callback(invocation_context=ic)
The returned value of type
Optional[types.Content]is ignored/discarded. Consequently, if a plugin returns a non-Nonevalue to stop execution and end the runner, the runner ignores it and schedules the root node/workflow anyway.In comparison, the sequential execution wrapper
_exec_with_pluginin runners.py:L1373-1393 correctly evaluates the callback return value:early_exit_result = await plugin_manager.run_before_run_callback( invocation_context=invocation_context ) if isinstance(early_exit_result, types.Content): early_exit_event = Event(...) ... yield early_exit_event else: # Continue execution...
-
Code References:
- Malfunctioning callback invocation: runners.py:L531-L532
- Reference working code structure: runners.py:L1373-L1393
2. Existing Pull Requests ("Is there a linked PR that fixes this issue?")
- Linked PR: None
- Analysis: Command
gh pr list --search "6013"returned no related PRs.
3. Recommendation
- Recommendation: Should Fix (High Priority)
- Rationale:
- Impact: Swallowing before-run callback outputs during workflow runs breaks the documented contract of interrupting runners cleanly. Under normal conditions, interrupting workflows early is highly critical to preventing unnecessary LLM charges and downstream orchestration side-effects.
- Implementation Complexity: Low. The fix is localized to
_run_node_asyncand matches the pattern employed by_exec_with_plugin. - Proposed Fix:
Replace runners.py:L531-L532 with:# Run before_run callbacks early_exit_result = await ic.plugin_manager.run_before_run_callback( invocation_context=ic ) if isinstance(early_exit_result, types.Content): early_exit_event = Event( invocation_id=ic.invocation_id, author='model', content=early_exit_result, ) _apply_run_config_custom_metadata( early_exit_event, ic.run_config ) await self.session_service.append_event( session=ic.session, event=early_exit_event, ) yield early_exit_event await ic.plugin_manager.run_after_run_callback(invocation_context=ic) if self.app and self.app.events_compaction_config: logger.debug('Running event compactor.') from google.adk.apps.compaction import _run_compaction_for_sliding_window await _run_compaction_for_sliding_window( self.app, session, self.session_service, skip_token_compaction=ic.token_compaction_checked, ) return
Summary of Completed Steps
- Retrieved & Parsed Issue: Fetched issue details for ADK 2.0 ignores before_run callback for Workflow #6013 via custom API tooling.
- Located & Analyzed MALFUNCTIONING Code: Traced workflow execution to the
_run_node_asyncrouting in runners.py and inspected the missing check onrun_before_run_callback. - Confirmed Non-existence of existing PRs: Polled repository PRs for references to issue ADK 2.0 ignores before_run callback for Workflow #6013.
- Delivered Premium Report: Drafted an exhaustive, structured triage report outlining the root-cause, alignment, and a reliable implementation fix.
- What is broken? When executing a workflow (or any other
Hello @mfojtak ,
To help us investigate this issue, could you please share your reproduction code, the steps to reproduce the behavior and any additional observations you have?
Additionally, you can refer to this page in the ADK documentation to verify your implementation.- addedrequest clarification[Status] The maintainer need clarification or more information from the author[Status] The maintainer need clarification or more information from the author
on Jun 10, 2026 Hello @llalitkumarrr
I cannot share the reproduction code as it is our internal protected code.
But the root of the problem is simple. In the new 2.0 implementation, the plugins are not working properly because of lineawait ic.plugin_manager.run_before_run_callback(invocation_context=ic)
which ignores return value of the run_before_run_callback function and violates the predefined behavior of any plugin.
The correct behavior is implemented in the old pre-2.0 code:
early_exit_result = await plugin_manager.run_before_run_callback( invocation_context=invocation_context ) if isinstance(early_exit_result, types.Content): early_exit_event = Event( invocation_id=invocation_context.invocation_id, author='model', content=early_exit_result, )
where the return value is checked for early exit.
This issue has been automatically marked as stale because it has not had recent activity for 7 days after a maintainer requested clarification. It will be closed if no further activity occurs within 7 days.
- addedstale[Status] Issues which have been marked inactive since there is no user response[Status] Issues which have been marked inactive since there is no user response
on Jun 26, 2026 This has been automatically closed because it has been marked as stale for over 7 days.
- added a commit that references this issue
on Aug 21, 2026
ADK 2.0 ignores before_run callback returning a value for workflows. In this case the execution should stop and no further llm calls should be executed.
Problem is in runner.py / _run_node_async where
swallows returned value and never checks if execution should continue
Steps to Reproduce:
Use Workflow as root_agent
Implement plugin and override before_run_callback
Expected Behavior:
Execution should stop if before_run_callback returns a value as described here:
"Returning a value to halt execution of the runner and ends the runner with that event"
Environment Details:
Model Information:
How often has this issue occurred?: