Skip to content

fix(a2a): stop writing per-caller headers onto the shared cached httpx client - #35978

Merged
yassin-berriai merged 1 commit into
litellm_internal_stagingfrom
litellm_a2a_shared_client_headers
Aug 7, 2026
Merged

fix(a2a): stop writing per-caller headers onto the shared cached httpx client#35978
yassin-berriai merged 1 commit into
litellm_internal_stagingfrom
litellm_a2a_shared_client_headers

Conversation

@yassin-berriai

@yassin-berriai yassin-berriai commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • Every A2A request mints a brand-new pooled httpx client
  • That fills the 200-entry cache every provider shares
  • Other providers' cached clients get evicted by A2A traffic
  • Cause: per-request headers are part of the client cache key
  • Once one client is shared, its cookie jar is shared too, so one agent's session cookie reaches another agent

How it solves it:

  • Headers ride the a2a SDK's per-call context, not the client
  • Agent card fetch gets them via resolver_http_kwargs
  • Cache key is back to timeout only
  • One pooled client now serves every A2A caller, and it stores and sends no cookies

Relevant issues

Linear ticket

Resolves LIT-5229

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

Before: the two changed source files restored to their 2792887e47 content, reproducible with git checkout 2792887e47 -- litellm/a2a_protocol/. After: commit fcedef4627. Section 3's before leg is narrower, since it isolates the one behaviour that section 1 and 2 introduce: it restores only litellm/a2a_protocol/main.py to 767319d1b7, the head this PR carried before the cookie change

The upstream here is a local recording agent rather than a hosted one, because the measurement is which connection, which headers and which cookies reach the far side, and only an endpoint that reports its own inbound requests can show that. This is the live proof only; the test suite itself opens no sockets

1. The shared client cache, driving the real create_a2a_client

250 calls, each with a distinct X-LiteLLM-Trace-Id exactly as the proxy generates them, with one non-A2A client seeded into the cache beforehand. The harness asserts it really did generate 250 distinct header sets, otherwise the number below would be measuring one cache key

Before:

  shared cache cap: 200
  cache entries before any A2A traffic: 5
  distinct header sets exercised:       250
  cache entries after 250 A2A requests: 200
  pre-existing non-A2A client survived: False

After:

  shared cache cap: 200
  cache entries before any A2A traffic: 5
  distinct header sets exercised:       250
  cache entries after 250 A2A requests: 6
  pre-existing non-A2A client survived: True

2. Connection reuse through a live proxy

Two agents against one upstream, 20 alternating A2A calls through a proxy on port 15229, upstream on 25229, with this config:

model_list: []

general_settings:
  master_key: sk-lit5229

agents:
  - agent_name: agent-a-lit5229
    agent_card_params:
      url: http://127.0.0.1:25229
      name: agent-a-lit5229
    static_headers:
      X-Agent-Tag: alpha-5229
  - agent_name: agent-b-lit5229
    agent_card_params:
      url: http://127.0.0.1:25229
      name: agent-b-lit5229
    static_headers:
      X-Agent-Tag: beta-5229
python litellm/proxy/proxy_cli.py --config config_lit5229.yaml --port 15229
AID_A=$(curl -s -H "Authorization: Bearer sk-lit5229" http://127.0.0.1:15229/v1/agents \
  | python -c "import sys,json; print([a['agent_id'] for a in json.load(sys.stdin) if a['agent_name']=='agent-a-lit5229'][0])")
AID_B=$(curl -s -H "Authorization: Bearer sk-lit5229" http://127.0.0.1:15229/v1/agents \
  | python -c "import sys,json; print([a['agent_id'] for a in json.load(sys.stdin) if a['agent_name']=='agent-b-lit5229'][0])")

for i in $(seq 1 20); do
  if [ $((i % 2)) -eq 1 ]; then AID=$AID_A; else AID=$AID_B; fi
  curl -s -X POST "http://127.0.0.1:15229/a2a/$AID/message/send" \
    -H "Authorization: Bearer sk-lit5229" -H "Content-Type: application/json" \
    -d "{\"jsonrpc\":\"2.0\",\"id\":\"req-$i\",\"method\":\"message/send\",\"params\":{\"message\":{\"role\":\"user\",\"parts\":[{\"kind\":\"text\",\"text\":\"hello\"}],\"messageId\":\"m-$i\"}}}"
done

lsof -nP -p $(pgrep -f "proxy_cli.py.*config_lit5229" | head -1) -iTCP:25229 | grep -c ESTABLISHED
curl -s http://127.0.0.1:25229/_dump

Before:

### sockets from the proxy to the upstream BEFORE any traffic
  0
### sending 20 A2A requests, alternating agent-a / agent-b
  successful responses: 20 / 20
### sockets from the proxy to the upstream AFTER the burst
  established connections held open by the proxy: 40
### what the upstream saw
  requests received:                 20
  distinct header sets exercised:    20   (distinct X-LiteLLM-Trace-Id)
  distinct upstream TCP connections: 20
  per-agent header still correct:    ['alpha-5229', 'beta-5229']

After:

### sockets from the proxy to the upstream BEFORE any traffic
  0
### sending 20 A2A requests, alternating agent-a / agent-b
  successful responses: 20 / 20
### sockets from the proxy to the upstream AFTER the burst
  established connections held open by the proxy: 2
### what the upstream saw
  requests received:                 20
  distinct header sets exercised:    20   (distinct X-LiteLLM-Trace-Id)
  distinct upstream TCP connections: 1
  per-agent header still correct:    ['alpha-5229', 'beta-5229']

All 20 calls succeed on both runs and each agent's own header still arrives on its own requests, so the difference is purely that 20 requests stop costing 20 connections and 40 held sockets

Those two numbers were captured at 767319d1b7, before the cookie change. Section 3's burst re-measures the connection count on the current head and gets the same 2

3. Cookie isolation between agents, through a live proxy

Same shape, on ports 15230 and 25230. The upstream answers agent-alpha's JSON-RPC call with Set-Cookie: a2a_session=only-alpha-may-hold-this and records the Cookie header on every request it receives, agent card fetches included. Two agents, one host, so both land on the same cookie domain

model_list: []

general_settings:
  master_key: sk-lit5229-cookie

agents:
  - agent_name: agent-alpha-5230
    agent_card_params:
      url: http://127.0.0.1:25230
      name: agent-alpha-5230
    static_headers:
      X-Agent-Tag: alpha
  - agent_name: agent-beta-5230
    agent_card_params:
      url: http://127.0.0.1:25230
      name: agent-beta-5230
    static_headers:
      X-Agent-Tag: beta
python litellm/proxy/proxy_cli.py --config config_5230.yaml --port 15230

KEY=sk-lit5229-cookie
ALPHA=$(curl -s -H "Authorization: Bearer $KEY" http://127.0.0.1:15230/v1/agents \
  | python -c "import sys,json; print([a['agent_id'] for a in json.load(sys.stdin) if a['agent_name']=='agent-alpha-5230'][0])")
BETA=$(curl -s -H "Authorization: Bearer $KEY" http://127.0.0.1:15230/v1/agents \
  | python -c "import sys,json; print([a['agent_id'] for a in json.load(sys.stdin) if a['agent_name']=='agent-beta-5230'][0])")

for pair in "$ALPHA:m-alpha" "$BETA:m-beta"; do
  AID=${pair%%:*}; MID=${pair##*:}
  curl -s -o /dev/null -w "%{http_code}\n" -X POST "http://127.0.0.1:15230/a2a/$AID/message/send" \
    -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
    -d "{\"jsonrpc\":\"2.0\",\"id\":\"$MID\",\"method\":\"message/send\",\"params\":{\"message\":{\"role\":\"user\",\"parts\":[{\"kind\":\"text\",\"text\":\"hello\"}],\"messageId\":\"$MID\"}}}"
done

curl -s http://127.0.0.1:25230/_dump

Before:

### agent-alpha calls its agent (this upstream answers with Set-Cookie)
  http status: 200
### agent-beta calls its agent, a different agent on the same host
  http status: 200
### what the upstream received
  card  from agent-tag alpha  Cookie: None
  rpc   from agent-tag alpha  Cookie: None
  card  from agent-tag beta   Cookie: a2a_session=only-alpha-may-hold-this
  rpc   from agent-tag beta   Cookie: a2a_session=only-alpha-may-hold-this

After:

### agent-alpha calls its agent (this upstream answers with Set-Cookie)
  http status: 200
### agent-beta calls its agent, a different agent on the same host
  http status: 200
### what the upstream received
  card  from agent-tag alpha  Cookie: None
  rpc   from agent-tag alpha  Cookie: None
  card  from agent-tag beta   Cookie: None
  rpc   from agent-tag beta   Cookie: None

Pooling is untouched by that. 20 further alternating calls on the same proxy, after the two above:

  established proxy -> upstream connections: 2
  upstream requests recorded:               44
  of those, requests carrying any Cookie:   0

Type

🐛 Bug Fix

Changes

create_a2a_client folded the caller's header set into the httpx client cache key, by way of str(sorted(extra_headers.items())) stuffed into the unrelated disable_aiohttp_transport param, so that it could then call headers.update(extra_headers) on the returned client without one caller's headers reaching the next. That works, but it pays for the isolation by giving every distinct header set its own client, and the proxy puts a fresh X-LiteLLM-Trace-Id into extra_headers on every request. So the key changed on every call. Each A2A request built a new httpx.AsyncClient, cached it under a key nothing would ever look up again, and pushed an entry into the 200-entry in_memory_llm_clients_cache that every provider shares

Measured above: 250 A2A requests take that cache from 5 entries to its cap and evict a previously cached non-A2A client. A2A traffic therefore destroys connection reuse for the whole proxy and gets none itself, holding two sockets per request open instead of pooling. Writing to a shared cached object was the other half of the same design, and it is only harmless today because of the cache key trick, so a future change that drops that one line would silently start bleeding one caller's headers into the next

The headers now travel with the request instead of living on the client. create_a2a_client asks the cache for a client keyed on timeout alone, hands the caller's headers to the agent card fetch through create_client(resolver_http_kwargs=...), and stashes a ClientCallContext(service_parameters=extra_headers) next to the existing _litellm_httpx_client handle. _send_message and _stream_messages pass that context down, and the SDK turns it into per-request headers=, which httpx merges over the client defaults. handle_a2a_localhost_retry carries the context onto the retry client the same way it already carried the httpx client, so a retried request keeps its headers

Headers are not the only per-caller state an httpx.AsyncClient carries. AsyncClient._send_single_request calls self.cookies.extract_cookies(response) on every response, and _merge_cookies replays the jar onto every outgoing request, so the moment all A2A callers share one client they share one cookie jar. Section 3 shows the consequence on a live proxy: an agent's session cookie arrives at a different agent on the same host, on that agent's card fetch and on its JSON-RPC call. The pooled client now carries DefaultCookiePolicy(allowed_domains=()), which rejects every domain in both directions, so nothing is stored and nothing is replayed. Nothing in litellm or in the a2a SDK reads cookies for A2A: the SDK's own auth interceptor skips API keys declared in: cookie and says so, and grep -ri cookie litellm/a2a_protocol/ matches only the three lines this PR adds. Blocking is also what the pre-PR proxy behaviour amounted to, since a per-request client jar never outlived its request

This is adjacent to LIT-4883, which covers memory growth in that same client cache. A workload minting one client per request is the profile that makes cache churn expensive, and this PR removes the churn at its source. It does not change any caching or eviction behaviour, so the two are independent

On tests, the claim that the SDK's call context becomes per-request headers is a claim about what reaches the transport, so tests/test_litellm/a2a_protocol/test_main.py seeds the shared client cache with a real httpx.AsyncClient backed by httpx.MockTransport and asserts on the httpx.Request objects that arrive there. No socket is opened, and the merge of per-request headers over client defaults, and httpx's own cookie handling, still happen for real inside httpx. Seeding has to run on the test's own event loop, since the client cache keys on it, and the fixture asserts the seeded handler is the one get_async_httpx_client hands back, so a drift in the cache key formula fails loudly instead of silently testing nothing

Being precise about which tests are regression evidence and which are guards, since they fail on the parent commit for different reasons:

test_create_a2a_client_leaves_the_shared_client_untouched is the regression test. On 2792887e47 it fails on its own assertion, Expected 'update' to not have been called. Called 1 times., with no network and nothing environmental. It pins both halves of the invariant: the shared client is never written to, and no header-derived param reaches the cache key

test_one_agents_session_cookie_never_reaches_another_agent is the regression test for the cookie half. Its mock upstream answers one tenant with a Set-Cookie, and it asserts the shared client's jar stays empty and that the next agent's card fetch and RPC carry no Cookie header. Two mutants, dropping the policy install and widening it to a plain DefaultCookiePolicy(), each fail it on their own run

The test_main.py group also fails on 2792887e47, but on connection refused rather than on an assertion, because the old cache key means the code never reaches the seeded client at all. That failure is a fair statement of the defect, and it is deterministic and offline since the URL is the discard port, but it is not an assertion-level catch and is not presented as one. Their real job is as guards: once every caller genuinely shares one client, test_each_caller_sends_only_its_own_headers across all three arrival orderings, plus its streaming and agent-card counterparts, fail loudly on any reintroduction of a write to the shared client's headers. Before this PR they could not do that, because each header set had its own client

test_create_a2a_client_uses_fresh_httpx_client in the proxy suite asserted the old contract, and was vacuous besides: its mock handed back a new client object per call whatever the code did, so it would have passed against code that ignored extra_headers entirely. It is the test replaced by the regression test above

Mutation check over eight mutants, one per changed call site, mutated and run separately so no mutant can hide behind another. Each edit asserts it changed the file and each run asserts pytest reported a test count, so a silently skipped mutation cannot read as a pass. Baseline 257 passed, restored 257 passed, 8 killed, 0 survived

KILLED   m1 non-streaming send drops the per-call context          ->  4 failed, 253 passed
KILLED   m2 streaming send drops the per-call context              ->  1 failed, 256 passed
KILLED   m3 agent card fetch stops getting the caller headers      ->  1 failed, 256 passed
KILLED   m4 call context is never stashed on the client            ->  4 failed, 253 passed
KILLED   m5 localhost retry drops the stashed call context         ->  1 failed, 256 passed
KILLED   m6 header set is folded back into the client cache key    ->  9 failed, 248 passed
KILLED   m7 pooled client keeps its cookie jar                     ->  1 failed, 256 passed
KILLED   m8 cookie policy accepts every domain                     ->  1 failed, 256 passed

QA runbook

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@greptile-apps

greptile-apps Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR moves caller-specific A2A headers from cached HTTP clients into per-call SDK context while retaining a shared connection pool

  • Passes headers separately for agent-card discovery and message calls
  • Preserves call context when rebuilding a client for localhost retry
  • Prevents the shared cookie jar from retaining upstream cookies
  • Replaces socket-based unit coverage with an injected httpx.MockTransport

Confidence Score: 5/5

The PR appears safe to merge

The previously reported socket-based test dependency is removed, and no blocking failure remains

Important Files Changed

Filename Overview
litellm/a2a_protocol/main.py Moves A2A headers into resolver and call-context arguments while sharing the cached HTTP client and disabling cookie persistence
litellm/a2a_protocol/exception_mapping_utils.py Carries the stored per-call context onto the client created for localhost retry
tests/test_litellm/a2a_protocol/test_main.py Uses a cached client backed by MockTransport to verify header isolation, pooling, streaming, card discovery, and cookie handling
tests/test_litellm/a2a_protocol/test_a2a_exception_mapping_utils.py Verifies that localhost retry preserves the original call context
tests/test_litellm/proxy/agent_endpoints/test_agent_header_isolation.py Updates the regression contract to ensure shared cached clients are not mutated or keyed by caller headers

Reviews (3): Last reviewed commit: "fix(a2a): stop writing per-caller state ..." | Re-trigger Greptile

Comment thread tests/test_litellm/a2a_protocol/test_main.py Outdated
Comment thread litellm/a2a_protocol/main.py Outdated
Comment on lines +223 to +224
def _get_a2a_call_context(a2a_client: "A2AClientType") -> Optional["A2ACallContextType"]:
"""Return the per-caller header context LiteLLM stashed at client creation, if any."""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 New production comments violate policy

This helper docstring and the additional explanatory comments in the A2A creation and retry paths violate the repository policy against newly written comments unless explicitly requested. Remove the new prose and keep the behavior self-explanatory through code structure and naming.

Context Used: CLAUDE.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread litellm/a2a_protocol/main.py
@veria-ai

veria-ai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Security review

No open security issues remain on this pull request.

Fixed/addressed: 1 · PR risk: 0/10

@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.90909% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
litellm/a2a_protocol/main.py 90.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@yassin-berriai
yassin-berriai force-pushed the litellm_a2a_shared_client_headers branch from 97fccf2 to 767319d Compare August 5, 2026 19:42
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

@greptileai please re-review 767319d

Both findings are addressed in that commit.

The localhost server in tests/test_litellm/a2a_protocol/test_main.py is gone. The tests now seed the shared client cache with a real httpx.AsyncClient backed by httpx.MockTransport and assert on the httpx.Request objects that reach it, so no socket is opened and the test stays in the file mapped to the module it covers, which is where CLAUDE.md puts a bug-fix regression test.

Every comment and docstring added to the production files is removed. The only comments remaining there are the two # pyright: ignore[...] suppressions, which CLAUDE.md requires to carry a reason, plus an edit to one pre-existing comment that the change had made inaccurate.

@codspeed-hq

codspeed-hq Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_a2a_shared_client_headers (fcedef4) with litellm_internal_staging (b66d4e6)1

Open in CodSpeed

Footnotes

  1. No successful run was found on litellm_internal_staging (b7749f6) during the generation of this report, so 60bde31 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

…client

create_a2a_client took the raw client off a process-wide cached handler and
called headers.update() on it, then leaned on folding the header set into the
cache key (through the unrelated disable_aiohttp_transport field) to keep one
caller's credentials away from the next.

Per-caller headers now ride with each request through the a2a SDK's call
context, and the agent card fetch gets them through resolver_http_kwargs, so
the shared client is never written to and its cache key no longer varies by
header set. Since the proxy puts a fresh trace id in every request's headers,
that key previously changed on every call, giving each request its own httpx
client and flushing the 200-entry client cache that every other provider
shares. All A2A callers on one timeout now reuse a single pooled client.

Sharing that client also means sharing its httpx cookie jar, which httpx fills
from every Set-Cookie and replays on any later request to a matching domain, so
one agent's session cookie would arrive at another agent on the same host. The
pooled client now carries a cookie policy that stores and sends nothing, which
neither litellm nor the a2a SDK relies on: the SDK's auth interceptor skips
cookie-borne API keys outright.
@yassin-berriai
yassin-berriai force-pushed the litellm_a2a_shared_client_headers branch from 767319d to fcedef4 Compare August 6, 2026 23:46
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

@greptileai please review the current head fcedef4. The commit was squashed and now also blocks cookie persistence on the pooled client

@yassin-berriai

Copy link
Copy Markdown
Contributor Author

@tin-berri your approval was against 767319d. Since then this also blocks cookie persistence on the pooled client, so worth a re-look

@yassin-berriai

Copy link
Copy Markdown
Contributor Author

osv-scan is red on litellm_internal_staging itself and every open branch: h2 and js-yaml in lockfiles this PR does not touch

@yassin-berriai

Copy link
Copy Markdown
Contributor Author

Correction: my cookie proof used 127.0.0.1, and aiohttp refuses cookies for IP hosts, so it hid a second jar. Do not merge, fixing

@yassin-berriai
yassin-berriai merged commit 2b38991 into litellm_internal_staging Aug 7, 2026
78 of 79 checks passed
@yassin-berriai
yassin-berriai deleted the litellm_a2a_shared_client_headers branch August 7, 2026 01:58
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

Follow-up in #36149: this only blocked the httpx jar. aiohttp is the default transport and its session keeps a second jar, so the leak is still live

yassin-berriai added a commit that referenced this pull request Aug 7, 2026
#35978 stopped the pooled A2A client replaying one upstream's Set-Cookie to
another by installing a blocking policy on that client's httpx cookie jar. That
covers only one of the two jars on the request path. AiohttpTransport is the
default transport unless it is explicitly disabled, and the aiohttp ClientSession
behind it keeps its own cookie jar which no httpx-level assertion can observe, so
the leak is still live on the default path: a live proxy on that commit still
delivers agent-alpha's session cookie to agent-beta's card fetch and JSON-RPC
call.

The reason it looked fixed is that aiohttp's default CookieJar is built with
unsafe=False and refuses to store cookies for IP hosts, so a proof addressed to
127.0.0.1 comes back clean whether or not that jar is blocked.

Cookie persistence is now blocked where the clients are built rather than at one
call site: blocked_cookie_jar() gives every httpx client, async and sync, a jar
whose DefaultCookiePolicy(allowed_domains=()) rejects every domain in both
directions, and both ClientSession constructions litellm owns, the transport's
session factory and the proxy's shared startup session, get a DummyCookieJar.
LiteLLM reads a response cookie nowhere, and an explicitly supplied Cookie header
still goes out, so passthrough forwarding and an agent's extra_headers are
unaffected. The A2A-scoped policy #35978 added is removed, since it is now dead.

The two suites that drive the aiohttp session factory synchronously mock
ClientSession because a real one needs a running event loop; DummyCookieJar has
the same requirement, so they mock it for the same reason.
yassin-berriai added a commit that referenced this pull request Aug 7, 2026
…too (#36149)

#35978 stopped the pooled A2A client replaying one upstream's Set-Cookie to
another by installing a blocking policy on that client's httpx cookie jar. That
covers only one of the two jars on the request path. AiohttpTransport is the
default transport unless it is explicitly disabled, and the aiohttp ClientSession
behind it keeps its own cookie jar which no httpx-level assertion can observe, so
the leak is still live on the default path: a live proxy on that commit still
delivers agent-alpha's session cookie to agent-beta's card fetch and JSON-RPC
call.

The reason it looked fixed is that aiohttp's default CookieJar is built with
unsafe=False and refuses to store cookies for IP hosts, so a proof addressed to
127.0.0.1 comes back clean whether or not that jar is blocked.

Cookie persistence is now blocked where the clients are built rather than at one
call site: blocked_cookie_jar() gives every httpx client, async and sync, a jar
whose DefaultCookiePolicy(allowed_domains=()) rejects every domain in both
directions, and both ClientSession constructions litellm owns, the transport's
session factory and the proxy's shared startup session, get a DummyCookieJar.
LiteLLM reads a response cookie nowhere, and an explicitly supplied Cookie header
still goes out, so passthrough forwarding and an agent's extra_headers are
unaffected. The A2A-scoped policy #35978 added is removed, since it is now dead.

The two suites that drive the aiohttp session factory synchronously mock
ClientSession because a real one needs a running event loop; DummyCookieJar has
the same requirement, so they mock it for the same reason.
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.

3 participants