Fixes the Plain T-10771 "Ad Words Sub Accounts" issue (thread).
Customer report (Chris Powers, overit.com — project pr_efBD7OODe0hG):
While the server can return a list of our sub accounts, it cannot seem to query that sub account in any manner, only returning the master MCC account data.
Root cause: apps/googleads/actions/search_stream_gaql.py pulled customer_id from metadata["headers"]["customer_id"] (the connection-level MCC ID populated by apps/googleads/config.ts) and used it directly in the URL path /v23/customers/{customer_id}/googleAds:searchStream. There was no way for the caller to target a sub-account. The Google Ads API requires:
customer_id = the target account (could be a sub-account)login-customer-id header = the manager (MCC) account ID — required when an MCC connection queries one of its client/sub-accountsFix:
customer_id field to SearchStreamGaqlRequest.login-customer-id header.Scope: First action in a planned per-action rollout. The same hard-coded pattern exists in 7 peer actions (mutate_campaigns, mutate_ad_groups, get_campaign_by_id, get_campaign_by_name, get_user_lists, create_customer_list, add_contact_to_customer_list) and should follow once this shape is validated by the customer. This PR keeps the diff scoped to the most-impacted action (the customer's reported use case is GAQL querying).
Verified locally by mocking http_request and capturing the outgoing URL + headers across six cases:
| # | Scenario | Expected URL path | login-customer-id header | Result |
|---|---|---|---|---|
| A | Default (no customer_id) | /v23/customers/{connection_id}/... | absent | ok |
| B | customer_id set to a sub-account | /v23/customers/{sub_id}/... | {connection_id} (normalized) | ok |
| C | customer_id set equal to connection (normalized) | /v23/customers/{connection_id}/... | absent | ok |
| D | customer_id non-numeric | — | — | ExecutionFailed with caller's value in the message |
| E | Connection ID malformed but request ID valid | /v23/customers/{request_id}/... | absent (defensive: don't inject garbage) | ok |
| F | Same call but check metadata["headers"] isn't mutated | — | — | original headers untouched |
$ ruff check apps/googleads/actions/search_stream_gaql.py
All checks passed!
$ python ci_checks/lint_url_path_not_encoded.py apps/googleads/actions/search_stream_gaql.py
Checked 1 file(s), 0 violation(s) in 0 file(s)
$ python ci_checks/lint_field_description_length.py apps/googleads/actions/search_stream_gaql.py
Checked 1 file(s), 0 violation(s) in 0 file(s)
Cortex auth note: I first dispatched Cortex fix-action (workflow bm1nd7ug) but it ended in FAILED_AUTH_ISSUE — Cortex doesn't have an MCC sub-account test connection. Per cortex-fix skill guidance, fell back to direct edit + mocked verification. Manual customer verification recommended before extending the pattern to peer actions: ask Chris Powers to retry his GAQL query with the new customer_id parameter set to a sub-account ID returned by GOOGLEADS_LIST_ACCESSIBLE_CUSTOMERS.
Triggered by: bharath@composio.dev | Source: slack Session: https://zen-api-production-4c98.up.railway.app/dashboard/#/chat/zen-c3fcc63d6e66