# How to Debug LangGraph Chat Workflow Issues with Checkpoint Storage

> Debug LangGraph chat workflow issues with checkpoint storage. Verify paths, inspect SQLite tables, enable logging, and clear stale checkpoints for smoother development.

- Repository: [Luis Novo/open-notebook](https://github.com/lfnovo/open-notebook)
- Tags: how-to-guide
- Published: 2026-06-29

---

**To debug LangGraph chat workflow issues with checkpoint storage in open-notebook, verify the SQLite checkpoint file path defined in [`open_notebook/config.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/config.py), inspect the database tables for malformed JSON or `NULL` states, enable debug logging for the `langgraph` namespace, and clear stale checkpoints either by deleting the file or truncating the tables programmatically.**

The open-notebook repository uses **LangGraph** to manage chat workflows, persisting intermediate state to a SQLite database via the `SqliteSaver` class. When messages go missing, context becomes stale, or the workflow crashes with database errors, the root cause typically lies in checkpoint corruption, incorrect file paths, or concurrent access conflicts. Understanding how the system stores and retrieves state allows you to diagnose these failures quickly without guessing.

## Understanding the Checkpoint Architecture

The chat workflow relies on a compiled LangGraph graph that writes execution snapshots to disk after each step. This persistence enables conversation continuity across API restarts, but introduces dependencies on the SQLite database file.

### Where Checkpoints Are Stored

The checkpoint file location is centralized in [`open_notebook/config.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/config.py) via the `LANGGRAPH_CHECKPOINT_FILE` variable. By default, this points to `./data/sqlite-db/checkpoints.sqlite`. You can verify the active path at runtime:

```python
from open_notebook.config import LANGGRAPH_CHECKPOINT_FILE
print(LANGGRAPH_CHECKPOINT_FILE)   # → ./data/sqlite-db/checkpoints.sqlite

```

If the application reads from an unexpected path, it may load stale data from an old development environment or fail to find any checkpoints at all.

### How SqliteSaver Integrates with the Graph

In [`open_notebook/graphs/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py), the workflow compiles with a `SqliteSaver` instance attached as the checkpointer. Line 88 configures the connection with `check_same_thread=False`, allowing the saver to operate across multiple threads:

```python

# From open_notebook/graphs/chat.py

memory = SqliteSaver.from_conn_string(LANGGRAPH_CHECKPOINT_FILE)
graph = agent_state.compile(checkpointer=memory)

```

The `SqliteSaver` creates and manages three internal tables: `checkpoints`, `snapshots`, and `metadata`. When the graph invokes a node, it reads the latest checkpoint from `checkpoints`, executes the step, and writes a new row with the updated state.

## Common Checkpoint-Related Issues

Workflow failures manifest in specific ways that indicate storage problems:

- **Missing messages**: The UI loads but previous turns appear blank, indicating the graph cannot deserialize the state from the `checkpoints` table.
- **Stale context**: Responses reference old data despite newer messages being sent, usually caused by the graph loading an outdated checkpoint row.
- **Database is locked**: SQLite allows only one writer at a time; concurrent chat requests trigger timeouts if the `check_same_thread=False` connection is contended.
- **Checkpoint corruption**: Malformed JSON in the `state` column or `NULL` values cause `IntegrityError` exceptions when LangGraph attempts to parse the snapshot.

## Step-by-Step Debugging Guide

Follow this sequence to isolate whether the issue stems from the checkpoint layer, the model invocation, or the surrounding application code.

### Verify the Checkpoint File Path

Before inspecting data, confirm the application points to the expected database file. If `LANGGRAPH_CHECKPOINT_FILE` is overridden by an environment variable or misconfigured in [`config.py`](https://github.com/lfnovo/open-notebook/blob/main/config.py), the workflow may read from a stale or empty database.

```python
import os
from open_notebook.config import LANGGRAPH_CHECKPOINT_FILE

# Check if file exists and print absolute path

if os.path.exists(LANGGRAPH_CHECKPOINT_FILE):
    print(f"Checkpoint DB found at: {os.path.abspath(LANGGRAPH_CHECKPOINT_FILE)}")
else:
    print("Warning: Checkpoint file does not exist. It will be created on first write.")

```

### Inspect the SQLite Database for Corruption

Open the checkpoint database with the `sqlite3` CLI or any SQLite viewer to examine the raw data.

```bash
sqlite3 ./data/sqlite-db/checkpoints.sqlite \
"SELECT rowid, created_at, state FROM checkpoints ORDER BY created_at DESC LIMIT 5;"

```

Look for these red flags:
- Rows where `state` is `NULL` or empty.
- Invalid JSON strings in the `state` column.
- Timestamps that do not align with recent chat activity.

Corrupted rows prevent LangGraph from reconstructing the `ThreadState`, causing the workflow to fail on the next invocation.

### Enable LangGraph Debug Logging

LangGraph respects Python’s standard logging configuration. Enable debug output for the `langgraph` namespace to see exactly when checkpoints are read, written, or skipped.

```python
import logging

logging.basicConfig(level=logging.DEBUG)
logging.getLogger("langgraph").setLevel(logging.DEBUG)

# Now invoke your graph; logs will show checkpoint operations

```

Watch for log entries indicating `IntegrityError` or failed deserialization. These confirm the graph is attempting to use the database but encountering data errors.

### Clear Corrupted or Stale Checkpoints

When corruption is confirmed or you suspect stale data is polluting the context, force a fresh start by clearing the database. You can delete the file entirely:

```bash
rm -f ./data/sqlite-db/checkpoints.sqlite

# The next API start will recreate an empty checkpoint DB

```

Alternatively, truncate the tables programmatically without deleting the file:

```python
import sqlite3
from open_notebook.config import LANGGRAPH_CHECKPOINT_FILE

conn = sqlite3.connect(LANGGRAPH_CHECKPOINT_FILE)
cur = conn.cursor()
cur.execute("DELETE FROM checkpoints;")
cur.execute("DELETE FROM snapshots;")
conn.commit()
conn.close()

```

### Handle Concurrent Access and Database Locks

The `SqliteSaver` in [`open_notebook/graphs/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py) (line 88) opens the connection with `check_same_thread=False`, permitting use across multiple threads. However, SQLite still enforces a single writer lock. If you observe `database is locked` errors:

- Reduce parallel executions by limiting the number of simultaneous chat requests.
- Consider switching to `aiosqlite` for asynchronous connection pooling.
- For production deployments, migrate from SQLite to PostgreSQL using LangGraph’s `PostgresSaver` for robust concurrency support.

### Validate Model Invocation Before Checkpoint Writes

The chat node defined in lines 30-80 of [`chat.py`](https://github.com/lfnovo/open-notebook/blob/main/chat.py) runs `call_model_with_messages`, which wraps `provision_langchain_model`. If an exception occurs during LLM invocation—such as an API timeout or rate limit—the error is caught by `classify_error` (lines 84-85) and raised as an `OpenNotebookError` before any checkpoint is written.

To confirm the model layer is functional:

1. Enable verbose logging around `provision_langchain_model`.
2. Check that the LLM returns valid output before the graph attempts to save state.
3. If the model fails, fix the underlying provider issue (API keys, quotas) before investigating checkpoint storage.

## Testing with a Minimal Workflow State

Isolate checkpoint issues from business logic by invoking the compiled graph with a fresh, minimal `ThreadState`. If this succeeds, the problem lies in persisted data, not the graph structure.

```python
from open_notebook.graphs.chat import graph, ThreadState

# Create a pristine state with no history

minimal_state: ThreadState = {
    "messages": [],
    "notebook": None,
    "context": None,
    "context_config": None,
    "model_override": None,
}

# Test synchronous invocation

result = graph.invoke(minimal_state)
print(result)

# Or for async environments:

# result = await graph.ainvoke(minimal_state)

```

A successful run with an empty `messages` list proves the `SqliteSaver`, `check_same_thread` configuration, and graph compilation are working correctly.

## Summary

- **Check the path**: Verify `LANGGRAPH_CHECKPOINT_FILE` in [`open_notebook/config.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/config.py) points to the expected SQLite file to avoid loading stale databases.
- **Inspect tables**: Query the `checkpoints`, `snapshots`, and `metadata` tables for `NULL` states or malformed JSON that indicate corruption.
- **Enable logging**: Set `langgraph` logger to `DEBUG` level to trace checkpoint read/write operations and catch `IntegrityError` details.
- **Clear safely**: Delete the `.sqlite` file or truncate tables programmatically to force a fresh checkpoint state when corruption is suspected.
- **Manage concurrency**: Remember the `check_same_thread=False` setting allows multi-threaded access but SQLite still locks on writes; scale horizontally with PostgreSQL if `database is locked` errors persist.
- **Test minimally**: Run `graph.invoke()` with an empty `ThreadState` to distinguish between checkpoint data issues and model provisioning failures.

## Frequently Asked Questions

### Where is the LangGraph checkpoint file located in open-notebook?

The checkpoint file location is defined by the `LANGGRAPH_CHECKPOINT_FILE` variable in [`open_notebook/config.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/config.py). The default value is `./data/sqlite-db/checkpoints.sqlite`. You can print this path at runtime by importing the variable and checking `os.path.abspath(LANGGRAPH_CHECKPOINT_FILE)`.

### How do I clear a corrupted checkpoint database?

You have two options. First, delete the file manually with `rm -f ./data/sqlite-db/checkpoints.sqlite` and restart the application, which will recreate an empty database. Second, connect via `sqlite3` and execute `DELETE FROM checkpoints; DELETE FROM snapshots;` to truncate the tables while preserving the file structure.

### Why does my chat workflow fail with a "database is locked" error?

This occurs because SQLite allows only one writer at a time. While [`open_notebook/graphs/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/graphs/chat.py) configures `check_same_thread=False` (line 88) to permit cross-thread usage, concurrent chat requests can still contend for the write lock. Reduce parallelism or migrate to PostgreSQL for production workloads requiring high concurrency.

### What tables does the SqliteSaver create in SQLite?

The `SqliteSaver` class creates and manages three tables: `checkpoints` (stores serialized state snapshots), `snapshots` (additional metadata about graph execution), and `metadata` (ancillary information about the saved state). Querying `checkpoints` directly is the fastest way to verify if state is persisting correctly between turns.