Skip to content

fix: route authentication responses to requesting sub-agents - #7203

Open
Junyi-Zheng wants to merge 2 commits into
google:mainfrom
Junyi-Zheng:fix/auth-response-routing
Open

Junyi-Zheng wants to merge 2 commits into
google:mainfrom
Junyi-Zheng:fix/auth-response-routing

Conversation

@Junyi-Zheng

Copy link
Copy Markdown

Link to Issue or Description of Change

Related: #7182

Problem: When a chat sub-agent with disallow_transfer_to_parent=True
requests credentials, the Runner selects the next agent before appending the
incoming authentication response. History-only routing can therefore send that
response to the root instead of the child that requested it, preventing the
protected tool from resuming.

Solution: Pass the incoming message to the router and resolve credential
responses against outstanding requests in the same session, after applying
rewinds. Use request IDs and persisted node paths to identify the requesting
agent, and require batched responses to belong to one agent and invocation.
Unmatched, already-answered, ambiguous, and ordinary messages retain the existing
routing behavior. Workflow and task-agent routing are preserved.

This is a focused authentication-response routing fix. It does not claim to
resolve every configuration or the original report's entire UI symptom.

Testing Plan

Validated commit: 2028cbd6ff9ad713d02f0985f6a4a1210840a5a5.
Base: 665ec9835bee154f6d30ff49fb5125d79ef57e8c.
Platform: macOS ARM64. Tests use the repository's test extra.

Unit tests

  • Added router edge-case tests and Runner regressions covering tool/toolset
    authentication, resumability on/off, direct/nested children, and memory/SQLite
    session storage.
  • All unit tests pass locally, except for one import-loading test that also fails on the unmodified base with the same dependencies. The full Python 3.10–3.14 matrix was run, and all new regression tests pass.
uv lock
tox run-parallel -p 2 --parallel-no-spinner \
  -x 'testenv.commands=pytest tests/unittests -n 4 --tb=short --junitxml={tox_root}/../adk-7182-investigation/release-validation/{env_name}.xml' \
  --result-json ../adk-7182-investigation/release-validation/tox-full.json

The checkout ignores uv.lock, although tox requires uv sync --locked; the
first attempt stopped during setup until a local lock was generated. The lock
is included in the validation attachment, not in the code changes. No tests
were deselected or newly marked xfail. Four pytest workers and two concurrent
tox environments only change execution parallelism and reporting.

Python Passed Failed Skipped Xfailed Xpassed
Python 3.10.21 15260 1 87 27 2
Python 3.11.16 15269 1 86 27 2
Python 3.12.14 15260 1 87 27 2
Python 3.13.15 15260 1 87 27 2
Python 3.14.7 15260 1 87 27 2

In each environment, the router/resume files record 61 passed, 1 skipped, 1 xfailed, including 16/16 passing restricted-child authentication regressions. Existing non-strict xpasses are reported separately.

The remaining failure is
test_import_loading.py::test_entry_point_loads_only_allowlisted_packages[agent].
Importing Agent loads authlib and its transitive dependencies outside the
existing allowlist. Running the same test against a git archive of the
unmodified base, with the same interpreters and installed dependencies,
reproduces the failure. This comparison is limited to the failing import tests;
it is not a claim that the full base suite was rerun.

The first Python 3.13 run selected Anaconda and also failed the runner import
case because it loaded sitecustomize. A fresh uv-managed Python 3.13 environment
was used for the final full rerun; both initial and rerun evidence are retained.

Wheel build and clean installation

uv build --out-dir ../adk-7182-investigation/release-validation/dist
cd ../adk-7182-investigation/release-validation
uv venv --python 3.12 wheel-venv
uv pip install --python wheel-venv/bin/python dist/google_adk-2.9.0-py3-none-any.whl
uv pip check --python wheel-venv/bin/python
wheel-venv/bin/adk --help
wheel-venv/bin/python -I ../probe.py --topology transfer_locked --output wheel-runner.json

Built the sdist and wheel successfully. The clean environment contains the
wheel and its declared runtime dependencies; pip check reports all 48 installed
packages compatible. Imports resolve to that environment's site-packages.
Both changed production modules in the wheel match the validated Git commit.
The CLI starts successfully.

The installed-wheel Runner reproduction passes 8/8 cases: tool/toolset ×
OIDC/OAuth2 × resumability off/on. Each case completes the protected tool exactly
once, raises no exception, and does not request authentication again.
This deterministic reproduction uses synthetic tokens, not a live identity
provider. The wheel SHA-256 is
80893288ff0af9e16b9a8e9aa418ef71730675aa3f81b7d59f927dec187e1e42.

Manual end-to-end tests

Ran the original ADK Web UI against a real local Keycloak 26.7.4 server with a
temporary realm/client/user. A deterministic offline model transfers to the
restricted child; the browser completes OAuth authorization, ADK exchanges the
real authorization code, and the child resumes its protected tool.

All four configurations—tool/toolset authentication × resumability off/on—
completed the Web automatic callback after establishing an SSO session. Each
produced child completed, one successful tool execution per session, and a
real Keycloak userinfo response with HTTP 200 and preferred_username=routing-user.
The first password-login check used a separately controlled tab and a manually
submitted /run callback; that assisted run is recorded separately and is not
counted as one of the four automatic Web runs.

The validation attachment's REPRODUCE.md includes the Docker command, agent
setup, browser steps, and expected output. It also includes the Runner probe,
test realm, and sanitized keycloak-evidence.json; real tokens and authorization
codes are omitted. No live Gemini model or production identity deployment was
tested.

Additional context

Additional experiments outside the submitted unit suite recorded 62 passed /
6 failed
across 68 cases. These include process restart with SQLite, user and
session isolation, delayed callbacks, sequential/concurrent duplicate callbacks,
same-named children, and failed-authentication retries.

Two limitations remain: simultaneous duplicate callbacks can execute a tool more
than once with in-memory resumable sessions, and some toolset authentication
retries after denial or a state mismatch request authentication again. An
upstream-router comparison with ordinary transferable children also reproduces
both behaviors (16 passed / 8 failed across 24 cases). That comparison changes
the transfer restrictions to avoid the original routing defect; it is not an
identical restricted-child baseline. This PR does not provide concurrent
exactly-once execution or repair those retry paths.

Authentication propagation through AgentTool is a separate issue and remains
out of scope. The base already contains upstream commit 2c61b844; this patch
does not require an unpublished downstream dependency.

Checklist

  • I have read the CONTRIBUTING.md document.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes, except for one import-loading test that fails on each supported Python version and also fails on the unmodified base with the same dependencies. All new regression tests pass.
  • I have manually tested my changes end-to-end.
  • Any dependent changes have been merged and published in downstream modules. (No new downstream changes required.)
    validation-evidence.zip

@google-cla

google-cla Bot commented Sep 19, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants