-
Notifications
You must be signed in to change notification settings - Fork 3k
Description
ApplicationIntegrationToolset ExecuteConnection requests return HTTP 400 (triggerId fragment not sent)
🔴 Required Information
Please ensure all items in this section are completed to allow for efficient triaging. Requests without complete information may be rejected / deprioritized. If an item is not applicable to you - please mark it as N/A
Describe the Bug:
When using ApplicationIntegrationToolset with an Integration Connectors connection (e.g. Salesforce) and entity operations (GET/LIST), the underlying POST to integrations.googleapis.com/.../ExecuteConnection:execute returns HTTP 400 "Request contains an invalid argument." The root cause is that the tool builds the URL with the trigger action in the fragment (e.g. ?triggerId=api_trigger/ExecuteConnection#get_EntityName). Per HTTP semantics, the fragment is never sent to the server; only path and query string are. So the ExecuteConnection API receives a request that is missing the full triggerId (the part after # is lost), and rejects it with 400.
Steps to Reproduce:
- Install ADK:
pip install "google-adk[a2a]>=1.25.1,<2.0.0"(oruv syncin a project that depends on it). - Create a GCP Integration Connectors connection (e.g. Salesforce) in a project/region, with at least one entity that supports GET and LIST (e.g. a custom object or any entity).
- Create an agent that uses
ApplicationIntegrationToolsetwith that connection andentity_operations={"YourEntity": ["GET", "LIST"]}. - Run the agent (e.g. via
Runner.run_async) and send a user message that triggers a GET or LIST tool call. - Observe: the tool’s POST to
ExecuteConnection:executereturns HTTP 400. In logs, the request URL shown by the HTTP client is.../ExecuteConnection:execute#get_EntityName(fragment only, notriggerIdin query), while the RestApiTool debug log may show?triggerId=api_trigger/ExecuteConnection#get_EntityName— the fragment is not sent to the server.
Expected Behavior:
The ExecuteConnection request should succeed (HTTP 200) and return the connector payload. The same request works when sent manually or via a custom tool that puts the full triggerId in the query string (e.g. triggerId=api_trigger/ExecuteConnection%23get_EntityName or by sending only the base triggerId if the API accepts it in the body).
Observed Behavior:
- Every GET and LIST tool call from ApplicationIntegrationToolset results in HTTP 400 from
integrations.googleapis.com. - Response body:
{"error": {"code": 400, "message": "Request contains an invalid argument.", "status": "INVALID_ARGUMENT"}}. - In httpx logs, the actual request URL is
.../integrations/ExecuteConnection:execute#get_EntityName(no query string withtriggerId), so the server does not receive the full trigger identifier.
Environment Details:
- ADK Library Version (pip show google-adk): 1.25.1 (constraint:
google-adk[a2a]>=1.25.1,<2.0.0) - Desktop OS: macOS (darwin 25.2.0)
- Python Version (python -V): Python 3.12.x
Model Information:
- Are you using LiteLLM: No
- Which model is being used: gemini-2.5-flash (Vertex AI)
🟡 Optional Information
Providing this information greatly speeds up the resolution process.
Regression:
Unknown. Same behavior observed with ADK 1.25.1. There is a unit test in adk-python for extracting embedded query params and fragments from the path (test_prepare_request_params_extracts_embedded_query_params in test_rest_api_tool.py, regression for issue #4555); the ApplicationIntegrationToolset path may not be passing the built URL through that logic, or the triggerId may be embedded in a way that still ends up in the fragment when the request is sent.
Logs:
Relevant snippet showing the mismatch between intended URL and actual request:
[INFO] httpx: HTTP Request: POST https://integrations.googleapis.com/v2/projects/.../locations/.../integrations/ExecuteConnection:execute#get_EntityName "HTTP/1.1 400 Bad Request"
[DEBUG] google_adk...rest_api_tool: API Response: POST https://integrations.googleapis.com/.../ExecuteConnection:execute?triggerId=api_trigger/ExecuteConnection#get_EntityName - Status: 400
The first line is what the HTTP client actually sent (URL has only fragment, no triggerId query). The second is what the RestApiTool reports (includes triggerId and fragment). The fragment is not sent over the wire, so the server receives an invalid or incomplete request.
Screenshots / Video:
N/A
Additional Context:
- The same GCP connection and entity work when calling ExecuteConnection directly (e.g. with
requests.post(url, json=payload)where the URL is built with?triggerId=api_trigger/ExecuteConnection#get_EntityName— in that case, the fragment is still not sent, but the server may accept the base triggerId and route using the request body; in our testing, a custom tool that sendstriggerId=api_trigger/ExecuteConnectionin the query string (no fragment) returns 200). - So the fix could be: ensure the full triggerId (including the part that AIT puts after
#) is sent in the query string (e.g. as a single parameter with#encoded as%23), not in the URL fragment, when building the request in the RestApiTool / OpenAPI path used by ApplicationIntegrationToolset.
Minimal Reproduction Code:
Requires a GCP project with an Integration Connectors connection (e.g. Salesforce). Replace YOUR_PROJECT, YOUR_REGION, YOUR_CONNECTION_NAME, and entity name as needed.
# agent.py
import os
from dotenv import load_dotenv
from google.adk.agents import Agent
from google.adk.tools.application_integration_tool import ApplicationIntegrationToolset
load_dotenv()
project = os.getenv("SALESFORCE_CONNECTION_PROJECT_ID")
location = os.getenv("SALESFORCE_CONNECTION_REGION")
connection = os.getenv("SALESFORCE_CONNECTION_NAME")
toolset = ApplicationIntegrationToolset(
project=project,
location=location,
connection=connection,
entity_operations={"YourEntity": ["GET", "LIST"]}, # use your connector's entity API name
tool_instructions="Use to get or list records.",
)
root_agent = Agent(
name="test_coordinator",
description="Test agent for connector.",
instruction="Use the tools to retrieve the requested record. For GET use only the bare record ID.",
model="gemini-2.5-flash",
tools=[toolset],
)Run with ADK Runner and send a message that triggers a GET (e.g. "Get the record with ID <record_id>"). The tool call will return 400.
How often has this issue occurred?:
- Always (100%)