How to Debug LangGraph Chat Workflow Issues with Checkpoint Storage

To debug LangGraph chat workflow issues with checkpoint storage in open-notebook, verify the SQLite checkpoint file path defined in 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 via the LANGGRAPH_CHECKPOINT_FILE variable. By default, this points to ./data/sqlite-db/checkpoints.sqlite. You can verify the active path at runtime:

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, 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:


# 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.

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, the workflow may read from a stale or empty database.

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.

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.

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:

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:

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 (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 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.

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 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. 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →