From 2ec72e8a4524d9a9c24acc8f24296094cb8669b3 Mon Sep 17 00:00:00 2001 From: A Vertex SDK engineer Date: Fri, 30 Oct 2026 10:55:19 -0700 Subject: [PATCH] fix: Fail open on any error resolving the project ID in the ADK framework. `AdkApp` converts a project number into a project ID with a Cloud Resource Manager call during `set_up()`, but tolerated only `PermissionDenied` and `Unauthenticated`. Any other failure -- including `google-cloud-aiplatform` not being installed, which surfaced as `UnboundLocalError` -- stopped the agent from starting. The lookup is cosmetic, so it now falls back to the project number on any error and logs a warning. PiperOrigin-RevId: 1010639435 --- agentplatform/frameworks/adk.py | 13 ++++-- .../frameworks/test_frameworks_adk.py | 43 +++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/agentplatform/frameworks/adk.py b/agentplatform/frameworks/adk.py index 35a7fa8066..bd03a547dc 100644 --- a/agentplatform/frameworks/adk.py +++ b/agentplatform/frameworks/adk.py @@ -2281,11 +2281,18 @@ def _get_project_id(self, project: str) -> Optional[str]: from google.cloud.aiplatform.utils import ( resource_manager_utils, ) - from google.api_core import exceptions return resource_manager_utils.get_project_id(project) - # Fail open as temporary workaround for identity_type config parameter - except (exceptions.PermissionDenied, exceptions.Unauthenticated): + # This runs from set_up() on the Agent Runtime cold-start path, so + # anything escaping here kills the uvicorn worker before it serves. + # The lookup is cosmetic -- every caller works with the project + # number -- so fail open on everything, including the import + # failing when google-cloud-aiplatform is not installed. + except Exception as e: # pylint: disable=broad-exception-caught + _warn( + "Failed to convert project number to project ID, proceeding" + f" with the project number: {e}" + ) return project return project or None diff --git a/tests/unit/agentplatform/frameworks/test_frameworks_adk.py b/tests/unit/agentplatform/frameworks/test_frameworks_adk.py index 1d010a824d..064072315c 100644 --- a/tests/unit/agentplatform/frameworks/test_frameworks_adk.py +++ b/tests/unit/agentplatform/frameworks/test_frameworks_adk.py @@ -24,6 +24,7 @@ import cloudpickle from google import auth +from google.api_core import exceptions as api_exceptions from google.api_core import operation as ga_operation from google.auth import credentials as auth_credentials from google.auth.transport import mtls @@ -1646,6 +1647,48 @@ def test_project_id_without_project( assert app._get_project_id(None) is None get_project_id_mock.assert_not_called() + @pytest.mark.parametrize( + "error", + [ + api_exceptions.PermissionDenied("denied"), + api_exceptions.Unauthenticated("unauthenticated"), + # b/561814776: gRPC UNKNOWN, seen when an Agent Gateway terminates + # the HTTP/2 stream to cloudresourcemanager. + api_exceptions.Unknown("Stream removed"), + api_exceptions.DeadlineExceeded("deadline exceeded"), + RuntimeError("something unexpected"), + ], + ) + def test_project_id_fails_open_on_lookup_error( + self, + get_project_id_mock: mock.Mock, + error: Exception, + ): + """A failed lookup falls back to the project number instead of raising. + + _get_project_id() runs from set_up() on the cold-start path, so an + escaping exception kills the worker over a lookup nothing depends on. + """ + app = adk_template.AdkApp(agent=_TEST_AGENT) + get_project_id_mock.side_effect = error + + assert app._get_project_id(_TEST_PROJECT_NUMBER) == _TEST_PROJECT_NUMBER + get_project_id_mock.assert_called_once_with(_TEST_PROJECT_NUMBER) + + def test_project_id_fails_open_when_lookup_module_is_missing(self): + """b/579673015: google-cloud-aiplatform is not installed. + + The failed import used to leave `exceptions` unbound, so the except + clause itself raised UnboundLocalError and set_up() failed. + """ + app = adk_template.AdkApp(agent=_TEST_AGENT) + with mock.patch.dict( + sys.modules, {"google.cloud.aiplatform.aiplatform.utils": None} + ): + assert ( + app._get_project_id(_TEST_PROJECT_NUMBER) == _TEST_PROJECT_NUMBER + ) + @pytest.mark.usefixtures("caplog") def test_enable_tracing( self,