#8942·langgraph

SqliteSaver.put raises TypeError on checkpoints whose metadata carries the legacy `writes` key (fixed for Postgres in #6236)

Author: HotragnCreated Sep 16, 2026Updated Sep 17, 2026
Labelsexternal

Checked other resources

  • This is a bug, not a usage question.
  • I added a clear and descriptive title that summarizes this issue.
  • I used the GitHub search to find a similar question and didn't find it.
  • I am sure that this is a bug in LangGraph rather than my code.
  • The bug is not resolved by updating to the latest stable version of LangGraph (or the specific integration package).
  • This is not related to the langchain-community package.
  • I posted a self-contained, minimal, reproducible example. A maintainer can copy it and run it AS IS.

Related Issues / PRs

  • #5769 — the same failure reported against PostgresSaver
  • #6236 — the merged fix for it, which added get_serializable_checkpoint_metadata()
  • #8701 / #7085 — adjacent but separate; see the last section

Reproduction Steps / Example Code (Python)

python
from langchain_core.messages import AIMessage
from langgraph.checkpoint.sqlite import SqliteSaver

config = {"configurable": {"thread_id": "1", "checkpoint_ns": ""}}
checkpoint = {
    "v": 1,
    "id": "1ef4f797-8335-6428-8001-8a1503f9b875",
    "ts": "2024-05-04T06:32:42.235444+00:00",
    "channel_values": {"messages": []},
    "channel_versions": {},
    "versions_seen": {},
}
# metadata exactly as an older LangGraph version wrote it
metadata = {
    "source": "loop",
    "step": 1,
    "writes": {"agent": {"messages": [AIMessage(content="hi")]}},
}

with SqliteSaver.from_conn_string(":memory:") as saver:
    saver.put(config, checkpoint, metadata, {})
    print("stored:", saver.get_tuple(config).metadata)

Error Message and Stack Trace (if applicable)

  File ".../issue_repro.py", line 21, in <module>
    saver.put(config, checkpoint, metadata, {})
  File ".../langgraph/checkpoint/sqlite/__init__.py", line 421, in put
    serialized_metadata = json.dumps(
  ...
TypeError: Object of type AIMessage is not JSON serializable

AsyncSqliteSaver.aput fails the same way at aio.py:534.

Description

Checkpoints written by older LangGraph versions record node output under metadata["writes"]. Those values are whatever the node returned — for a chat graph, message objects. The SQLite metadata column is JSON, and put() serializes with a plain json.dumps, so writing such a checkpoint back raises and the write is lost.

This is the same bug as #5769, which was reported against PostgresSaver. #6236 fixed it by adding get_serializable_checkpoint_metadata() to langgraph.checkpoint.base — it wraps get_checkpoint_metadata() and pops writes — and pointing the Postgres savers at it. The SQLite savers were not updated and still call get_checkpoint_metadata():

call site helper
checkpoint-postgres/.../postgres/__init__.py:342 get_serializable_checkpoint_metadata
checkpoint-postgres/.../postgres/aio.py:302 get_serializable_checkpoint_metadata
checkpoint-postgres/.../postgres/shallow.py:447, :785 get_serializable_checkpoint_metadata
checkpoint-sqlite/.../sqlite/__init__.py:422 get_checkpoint_metadata ← raises
checkpoint-sqlite/.../sqlite/aio.py:535 get_checkpoint_metadata ← raises

So the two backends currently disagree about whether the same put() succeeds. On main (230927fb3) the snippet above raises on SQLite; the identical metadata goes through Postgres, which drops the key and stores the rest.

Two things worth flagging before anyone fixes this:

It is a behaviour change for a JSON-serializable writes. SQLite persists {"writes": {"foo": "bar"}} today and would stop. That is the call #6236 already made for Postgres, so the fix brings the backends into agreement rather than introducing a new asymmetry — but it is a visible change, not purely a crash fix.

The SQLite test fixtures currently carry writes, and two of them need it. test_search_where and test_metadata_predicate feed metadata_1/metadata_2 to the SQL predicate builder as filter inputs, and they are the only tests covering a nested mapping being JSON-encoded into a bound parameter (writes: {"foo": "bar"}'{"foo":"bar"}'). Deleting writes from the shared fixtures the way #6236 did for Postgres silently removes that coverage. Those two tests need their own filter dicts.

InMemorySaver (checkpoint/.../memory/__init__.py:454) also calls get_checkpoint_metadata(), but it serializes with msgpack, so it stores the key instead of raising. Different severity, different package — I have left it out of scope here.

I have the SQLite fix ready on fix/sqlite-metadata-writes: both savers moved to the shared helper, the two predicate tests decoupled so the nested-mapping coverage survives, and a sync and async regression test for the snippet above. 119 passed in libs/checkpoint-sqlite including the conformance delta suite; reverting either saver line individually fails exactly one of the two new tests. ruff check, ruff format --check and ty check are clean. I could not run tests/test_ttl.py (needs pytest-retry) or anything Postgres (no Docker here), and this change touches neither.

Could I be assigned this? Happy to open the PR as soon as I am.

Not overlapping with: #8701 (Whxuan0701) asks for conformance coverage of nested JSON-compatible metadata across savers, and states "do not change serialization formats" as a non-goal — the opposite of this change; I checked that my branch leaves nested mappings, empty maps and empty lists round-tripping unchanged, so nothing there is silently satisfied. #7085 (Charank18) adds unit tests for the checkpoint.base helpers in libs/checkpoint/tests/test_base.py; my change is in libs/checkpoint-sqlite and touches no file it does. I also checked the nine open PRs against the two SQLite saver files (#8557, #8544, #8242, #7207, #7204, #7180, #7105, #6939, #6871) — all delta replay, ordering or pagination, none touches metadata serialization.

System Info

langgraph                    1.2.11
langgraph-checkpoint         4.2.0
langgraph-checkpoint-sqlite  3.1.1
langchain-core               1.6.1
Python                       3.13.12
OS                           Windows 11