# LangGraph: "'list' object has no attribute 'root'" after resume with a Pydantic RootModel in state

*Disclosure: I work on MemorySync (I'm Mohammed, the founder). I drafted this page with AI assistance and checked it against the source issue, the langgraph-checkpoint source and the Pydantic docs listed under Sources on Oct 29, 2026. I didn't run a reproducer for this page.*

## The question

In [langchain-ai/langgraph#9480](https://github.com/langchain-ai/langgraph/issues/9480) (opened Oct 29, 2026), a graph that keeps a Pydantic v2 `RootModel` in state works until it's resumed from a persistent checkpointer:

```python
class IntList(RootModel[list[int]]):
    pass

class State(TypedDict):
    items: IntList

def consume(state: State) -> State:
    return {"items": IntList(state["items"].root + [3])}
```

The first `invoke` stops at an `interrupt()`. Resuming with `Command(resume="go")` on a `SqliteSaver` fails with:

```text
AttributeError: 'list' object has no attribute 'root'
```

The serializer alone shows it: `serde.loads_typed(serde.dumps_typed(IntList([1, 2, 3])))` comes back as a plain `list`. Reported on langgraph 1.2.18 and langgraph-checkpoint 4.3.1 (the latest checkpoint release on PyPI as of Oct 29, 2026), still the latest checkpoint release on Nov 7, 2026.

**Status (checked Nov 7, 2026):** open, 4 comments, no merged fix. A community PR (#9496) was closed unmerged on Oct 30, and a commenter who verified the bug says a fix with RootModel regression tests is ready. langgraph-checkpoint 4.3.1 is still the latest release, and its `jsonplus.py` still rebuilds Pydantic v2 models with `cls(**...)`.

> **Skip the memory plumbing around this fix.** Once the fix below is in, the [MemorySync LangGraph guide](https://docs.memorysync.io/guides/langgraph?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_langgraph-rootmodel-checkpoint-list-object-has-no-attribute-root) shows how to add per-user memory with MemorySync. MemorySync doesn't fix this error itself. When you're ready, [see MemorySync](https://memorysync.io/?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_langgraph-rootmodel-checkpoint-list-object-has-no-attribute-root). Founding offer: use code FOUNDING30 for 30% off Starter for your first 3 months (Settings → Billing).

## Why it happens

In `langgraph/checkpoint/serde/jsonplus.py`, Pydantic v2 objects are written as `EXT_PYDANTIC_V2` with `obj.model_dump()` as the payload. On load, the code imports the class and calls `cls(**tup[2])`, falling back to `cls.model_construct(**tup[2])`; if that also fails, the outer handler hands back the raw payload.

For a `RootModel`, `model_dump()` returns the root value itself (a list, string, int...), not a dict. `cls(**[1, 2, 3])` can't work, so you get the bare list. Inside one run you don't notice, because the in-memory channel still holds the original object. After a checkpoint round trip (`get_state()`, or resume after `interrupt`), the next node gets the list.

## Fix 1: wrap the root value in a normal model

Regular `BaseModel` subclasses dump to a dict and rebuild through `cls(**payload)`, which is the path that works today. The issue's related reports (#9343 aliased fields, #6102 generic models) cover other edge cases, so keep the wrapper plain:

```python
from pydantic import BaseModel

class IntList(BaseModel):
    items: list[int]

class State(TypedDict):
    items: IntList

def consume(state: State) -> State:
    return {"items": IntList(items=state["items"].items + [3])}
```

## Fix 2: store the plain value, validate at the edges

Keep `list[int]` in graph state and build the `RootModel` inside the node when you need it:

```python
class State(TypedDict):
    items: list[int]

def consume(state: State) -> State:
    items = IntList.model_validate(state["items"])
    return {"items": items.root + [3]}
```

`model_validate` on a `RootModel` takes the root value directly ([Pydantic docs on `RootModel`](https://docs.pydantic.dev/latest/concepts/models/#rootmodel-and-custom-root-types)).

## Fix 3: patch the load path (what the issue proposes)

The reporter's suggested change, not merged as of Nov 7, 2026, rebuilds root models with `model_validate` instead of `cls(**payload)`:

```python
cls = getattr(importlib.import_module(tup[0]), tup[1])
if getattr(cls, "__pydantic_root_model__", False):
    return cls.model_validate(tup[2])
```

They say it keeps the existing payload format, so checkpoints already written would load correctly once it ships. Until then, Fix 1 or Fix 2 avoids carrying a fork.

## Checklist

1. Search your state schemas for `RootModel` subclasses.
2. Swap them for a wrapped `BaseModel` (Fix 1) or plain values (Fix 2).
3. Old checkpoints already hold the raw value, so make the resuming node accept both shapes, or start new threads.
4. If `get_state()` returns plain values for other models, check the log: the serializer logs "Blocked deserialization of ..." when a class isn't in `allowed_msgpack_modules`.
5. Watch #9480 for a fixed langgraph-checkpoint release.

## Common questions

**Why does it only fail after an interrupt?**
Within one run, the channel keeps the original object. Resume reads from the checkpoint, which is where the type is lost.

**Does switching from SQLite to Postgres help?**
The issue points at the shared `JsonPlusSerializer`, not the SQLite saver, so expect the same result with other savers that use it.

**Is this the same as the generic model issue?**
No. #6102 covers generic Pydantic models and #9343 aliased fields; this one is `RootModel` only.

## Skip the plumbing with MemorySync

Disclosure: I build MemorySync.

It doesn't fix this. The [MemorySync LangGraph guide](https://docs.memorysync.io/guides/langgraph?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_langgraph-rootmodel-checkpoint-list-object-has-no-attribute-root) says thread state (checkpoints, resume, time travel) belongs to a checkpointer such as `langgraph-checkpoint-postgres`, `-sqlite` or `-redis`, and MemorySync isn't one. What it adds is long-term memory across threads: `MemorySyncStore`, a LangGraph `BaseStore` you compile in with `store=`, plus pre-model context injection and a search tool. Read the [guide](https://docs.memorysync.io/guides/langgraph?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_langgraph-rootmodel-checkpoint-list-object-has-no-attribute-root) first. For an overview of MemorySync, see [the MemorySync homepage](https://memorysync.io/?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_langgraph-rootmodel-checkpoint-list-object-has-no-attribute-root).

From the guide:

**Unverified (not run):**

```bash
pip install "langchain-memorysync[langgraph]"
```

```python
from langchain_memorysync.langgraph import MemorySyncStore

store = MemorySyncStore()  # MEMORYSYNC_API_KEY from the env

store.put(("memories", "user-1"), "diet", {"text": "vegetarian"})

item = store.get(("memories", "user-1"), "diet")
print(item.value)  # {'text': 'vegetarian'}

hits = store.search(("memories", "user-1"), query="what do they eat?")
print(hits[0].value, hits[0].score)

# Filters use the same JSONB-style operators as LangGraph's own stores.
store.search(("memories", "user-1"), filter={"confidence": {"$gte": 0.8}})
```

*Snippet adapted from the [MemorySync LangGraph guide](https://docs.memorysync.io/guides/langgraph?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_langgraph-rootmodel-checkpoint-list-object-has-no-attribute-root); I haven't run it for this page.*

**Try it:** MemorySync is optional here: the steps above work without it. If you want the memory layer from the snippet, [see MemorySync](https://memorysync.io/?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_langgraph-rootmodel-checkpoint-list-object-has-no-attribute-root), create an API key in the dashboard and drop the snippet into your agent.

## Sources

- github.com/langchain-ai/langgraph/issues/9480 (opened 2026-10-29, open, 4 comments; read 2026-11-07)
- langgraph-checkpoint source: libs/checkpoint/langgraph/checkpoint/serde/jsonplus.py, main branch (`EXT_PYDANTIC_V2` dump/load; read 2026-10-29)
- docs.pydantic.dev/latest/concepts/models/ (`RootModel`; read 2026-10-29)
- MemorySync docs: docs.memorysync.io/guides/langgraph

**Next step:** read the [MemorySync LangGraph guide](https://docs.memorysync.io/guides/langgraph?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_langgraph-rootmodel-checkpoint-list-object-has-no-attribute-root), then [see MemorySync](https://memorysync.io/?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_langgraph-rootmodel-checkpoint-list-object-has-no-attribute-root). Founding offer: use code FOUNDING30 for 30% off Starter for your first 3 months (Settings → Billing).

