This fixture reproduces the transaction-boundary problem reported in LangGraph issue #8590: SqliteStore.batch() can raise on a later operation while an earlier mutation in the same batch remains committed.
- Package:
langgraph-checkpoint-sqlite==3.1.1 - Path tested by XBSTACK: synchronous
SqliteStore.batch() - Database: in-memory SQLite
- Network/API/model calls: none
- Date independently reproduced: 2026-08-12
The upstream issue also reports the equivalent behavior for AsyncSqliteStore.abatch(). XBSTACK has not independently rerun the async reproduction in this fixture yet, so this repository does not claim an independent async verification.
python -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt
python repro/repro_sync.pyExpected output on the affected version:
langgraph-checkpoint-sqlite 3.1.1
caught TypeError
original_expiration 2000-01-01 00:00:00
actual_expiration <a newer timestamp>
partial_commit_reproduced True
The important result is not the exact refreshed timestamp. It is that actual_expiration differs from original_expiration even though the batch raised TypeError.
A caller can reasonably assume a failed batch did not persist any earlier mutation. On the affected path that assumption is unsafe: an earlier TTL refresh can survive a later serialization failure, leaving the store partially updated.
Until an upstream fixed release is independently verified, do not treat SqliteStore.batch() as an atomic transaction boundary for failure-sensitive application logic. Pre-validate fallible inputs where possible, avoid mixing earlier mutations with later operations that can fail when atomicity matters, and add a pinned regression test before upgrading or removing any application-level guard.
This fixture demonstrates one deterministic partial-commit path. It does not prove that every batch() workload partially commits, and it does not establish that SQLite itself is at fault. The upstream report identifies the LangGraph SQLite store cursor context managers' unconditional COMMIT behavior as the implementation-level cause and proposes rollback-on-exception semantics.
This pinned fixture covers one failure class, not all LangGraph persistence semantics. Three issues require independent verification:
| Failure class | Verification target | XBSTACK evidence |
|---|---|---|
| Partial batch commit (#8590) | An earlier mutation persists despite a later batch exception | Independently reproduced here with langgraph-checkpoint-sqlite==3.1.1, synchronous SqliteStore.batch() |
| Deleted SQLite/WAL bytes (#9362) | Deleted API data may remain in local SQLite/WAL/backup storage | Upstream report only; no erasure benchmark in this repo |
| Checkpoint set[tuple] shape (#9516) | Resume may restore a collection in a different shape | Separate XBSTACK experiment and upstream discussion; not tested by this fixture |
All three upstream issues were open when checked on November 2, 2026. Do not infer a fixed release or a general SQLite defect from a pinned fixture. In production, pin versions and keep rollback-on-error, API-level deletion, physical retention/backups, and post-resume type assertions separate. Do not run file-erasure or WAL maintenance commands on a live database solely on an upstream report.
- English: Checkpointer choice, SQLite / Redis / Postgres, recovery and deletion limits
- 中文:LangGraph Checkpointer 选型、SQLite / Redis / Postgres 与删除边界
- Upstream issue: langchain-ai/langgraph#8590
- XBSTACK write-up: https://www.xbstack.com/en/ai/langgraph-checkpointer-memory-sqlite-redis/?utm_source=github&utm_medium=referral&utm_campaign=langgraph_sqlite_partial_commit&utm_content=repository_readme
- Status when checked on 2026-08-12: open, no linked pull request or fixed release shown on the issue page.
See version-matrix.md and logs/ for the pinned verification record.