## TL;DR
Restore fails because the `thread_id` in the config doesnt match any stored checkpoint: the agent generated a new id, typoed it, or the checkpointer was swapped. Fix it by logging the exact thread_id at write time, passing the identical config (including checkpoint_ns for subgraphs) at restore time, and listing stored threads to find the real one.

```text
agent's langgraph checkpoint restore failed: thread_id mismatch
```

## Use this when
- Checkpoint restore returns empty for a thread you know ran
- An agent passes a thread_id that doesnt match the original
- Subgraph restores fail while top-level restores work

## Not for this skill when
- The checkpoint data is corrupt (thats the stored state)
- Updates fail validation (thats the update shape)
- No checkpointer is configured (thats setup)

## Steps

1. Log the thread_id at write time and at restore time, then compare:

```python
config = {"configurable": {"thread_id": "user-123"}}
print("writing with", config["configurable"]["thread_id"])
# ... later, at restore:
print("restoring with", restore_config["configurable"]["thread_id"])
```
Expected output: identical strings. Agents commonly regenerate the id per run (uuid4) instead of reusing the conversation's id.

2. List the threads the checkpointer actually holds:

```python
# for SqliteSaver / PostgresSaver, query the checkpoints table
```
Expected output: the real stored thread ids. If yours isnt there, the write went elsewhere (different DB file, different checkpointer instance).

3. Include the checkpoint namespace for subgraph restores:

```python
config = {"configurable": {"thread_id": "user-123", "checkpoint_ns": "subgraph_ns"}}
state = app.get_state(config)
```
Expected output: the subgraph state returns. Subgraph checkpoints live under their namespace; restoring without it looks in the wrong place.

4. Make the agent reuse one thread_id per conversation:

```text
Thread rule: generate the thread_id once at conversation start, store it,
and pass the same value to every invoke and get_state call.
```
Expected output: restores start working. The id is a conversation identity, not a per-call nonce.

## Variant phrasings

### restore works in tests, fails in production
Tests use a fixed id; production generates one per request. The mismatch is in the id lifecycle, not the checkpointer.

### get_state returns None with no error
That is the mismatch signature: no exception, just nothing found. It means "no checkpoint for that id," not "checkpoint broken."

## Why it happens
The checkpointer keys everything by the configurable thread_id (plus namespace). Any difference, a fresh uuid, a typo, a missing namespace, an entirely different database file, addresses a different (empty) slot. The API returns None rather than raising, so agents interpret it as "no history" and start over, losing the conversation silently.

## Edge cases
- Two checkpointer instances against the same logical store (e.g. two SQLite files) never share threads; verify the file path matches.
- Deleting old checkpoints (TTL cleanup) can remove the thread you want to restore; check retention policy before assuming a bug.
- `checkpoint_id` pinning restores a specific version; omitting it gets the latest, which is usually what you want.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_8EPxMntdrDScP5X2vwim8A
