Skip to content

JsonPlusSerializer can silently replace restored checkpoint values with None after deserialization failure #6970

@yangbaechu

Description

@yangbaechu

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.

Reproduction Steps / Example Code (Python)

import importlib
import os
import sys
import tempfile
from pathlib import Path

from langgraph.checkpoint.base import empty_checkpoint
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.checkpoint.serde.jsonplus import JsonPlusSerializer


with tempfile.TemporaryDirectory() as temp_dir:
    module_path = Path(temp_dir) / "temp_model.py"
    module_path.write_text(
        "from dataclasses import dataclass\n"
        "@dataclass\n"
        "class SavedObject:\n"
        "    value: int\n"
    )

    sys.path.insert(0, temp_dir)
    module = importlib.import_module("temp_model")
    SavedObject = module.SavedObject

    saver = InMemorySaver(
        serde=JsonPlusSerializer(
            allowed_msgpack_modules=(("temp_model", "SavedObject"),)
        )
    )

    config = {
        "configurable": {
            "thread_id": "thread-1",
            "checkpoint_ns": "",
        }
    }

    checkpoint = empty_checkpoint()
    checkpoint["channel_values"]["state"] = SavedObject(123)
    checkpoint["channel_versions"]["state"] = 1

    saved_config = saver.put(
        config,
        checkpoint,
        metadata={"source": "loop", "step": 1},
        new_versions={"state": 1},
    )

    sys.modules.pop("temp_model", None)
    os.remove(module_path)
    importlib.invalidate_caches()

    restored = saver.get_tuple(saved_config)
    assert restored is not None
    value = restored.checkpoint["channel_values"]["state"]

    # The restored value becomes None after deserialization failure.
    assert value is not None

Error Message and Stack Trace (if applicable)

Description

I'm using a LangGraph checkpointer to restore checkpointed state that includes a custom dataclass. In the example code, checkpointing succeeds first, then the original module is removed before the checkpoint is loaded again. I expect deserialization failure to either be surfaced explicitly or handled in a way that preserves the serialized value, but the restored value is silently replaced with None.

Also reproduced on main at 79a756.

System Info

System Information

OS: Linux
OS Version: #1 SMP PREEMPT_DYNAMIC Thu Jun 5 18:30:46 UTC 2025
Python Version: 3.10.12 (main, Jan 26 2026, 14:55:28) [GCC 11.4.0]

Package Information

langchain_core: 1.2.16
langchain: 1.2.10
langsmith: 0.7.9
langchain_openai: 1.1.10
langgraph_sdk: 0.3.9

Optional packages not installed

deepagents
deepagents-cli

Other Dependencies

httpx: 0.28.1
jsonpatch: 1.33
langgraph: 1.0.10
openai: 2.24.0
orjson: 3.11.7
packaging: 26.0
pydantic: 2.12.5
pyyaml: 6.0.3
requests: 2.32.5
requests-toolbelt: 1.0.0
tenacity: 9.1.4
tiktoken: 0.12.0
typing-extensions: 4.15.0
uuid-utils: 0.14.1
xxhash: 3.6.0
zstandard: 0.25.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions