-
Notifications
You must be signed in to change notification settings - Fork 4k
Description
Checked other resources
- This is a bug, not a usage question. For questions, please use the LangChain Forum (https://forum.langchain.com/).
- I added a clear and detailed title that summarizes the issue.
- I read what a minimal reproducible example is (https://stackoverflow.com/help/minimal-reproducible-example).
- I included a self-contained, minimal example that demonstrates the issue INCLUDING all the relevant imports. The code run AS IS to reproduce the issue.
Example Code
from dataclasses import dataclass
from typing import Literal, Optional, TypedDict
from langgraph.checkpoint.postgres import PostgresSaver
from langgraph.graph import StateGraph, START, END
from langgraph.types import Command, interrupt
@dataclass
class Context:
"""Context schema defined by the developer."""
user_id: str
db_connection: str
class ApprovalState(TypedDict):
action_details: str
status: Optional[Literal["pending", "approved", "rejected"]]
def approval_node(state: ApprovalState) -> Command[Literal["proceed", "cancel"]]:
# Expose details so the caller can render them in a UI
decision = interrupt({
"question": "Approve this action?",
"details": state["action_details"],
})
# Route to the appropriate node after resume
return Command(goto="proceed" if decision else "cancel")
def proceed_node(state: ApprovalState):
return {"status": "approved"}
def cancel_node(state: ApprovalState):
return {"status": "rejected"}
builder = StateGraph(ApprovalState)
builder.add_node("approval", approval_node)
builder.add_node("proceed", proceed_node)
builder.add_node("cancel", cancel_node)
builder.add_edge(START, "approval")
builder.add_edge("proceed", END)
builder.add_edge("cancel", END)
# Use a more durable checkpointer in production
# checkpointer = MemorySaver()
checkpointer_cm = PostgresSaver.from_conn_string(
"postgresql://xxx:xxx@localhost:5432/postgres"
)
with checkpointer_cm as checkpointer:
graph = builder.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "approval-123"}}
initial = graph.invoke(
{"action_details": "Transfer $500", "status": "pending"},
config=config,
)
print(initial["__interrupt__"]) # -> [Interrupt(value={'question': ..., 'details': ...})]
result = graph.get_state(config)
# Resume with the decision; True routes to proceed, False to cancel
resumed = graph.invoke(Command(resume=True), config=config)
print(resumed["status"]) # -> "approved"
result = graph.get_state(config)
resumed = graph.invoke(Command(resume=True), config=config)
print(resumed["status"]) # -> "approved"
result = graph.get_state(config)
print("test")Error Message and Stack Trace (if applicable)
thread_id | checkpoint_ns | checkpoint_id | task_id | idx | channel | type |
blob
task_path
----------------------------+---------------+--------------------------------------+--------------------------------------+-----+--------------------+---------+----
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------
approval-123 | | 1f0e0728-95db-6158-bfff-aae7f05d1fa1 | c9699148-6d9d-4d35-3417-efc0e235fda8 | 0 | action_details | msgpack | \xa
_pregel_pull, __start__
approval-123 | | 1f0e0728-95db-6158-bfff-aae7f05d1fa1 | c9699148-6d9d-4d35-3417-efc0e235fda8 | 1 | status | msgpack | \xa
_pregel_pull, __start__
approval-123 | | 1f0e0728-95db-6158-bfff-aae7f05d1fa1 | c9699148-6d9d-4d35-3417-efc0e235fda8 | 2 | branch:to:approval | null | \x
_pregel_pull, __start__
approval-123 | | 1f0e0728-95e2-66a2-8000-885ac3f329f9 | d01e943c-7a45-f535-002f-4e2bb0d1ba12 | -3 | __interrupt__ | msgpack | \x9
465727275707482a576616c756582a87175657374696f6eb4417070726f7665207468697320616374696f6e3fa764657461696c73ad5472616e736665722024353030a26964d920353539643733346265316
_pregel_pull, approval
\x617070726f76616c2d313233 | | 1f0e0728-95e2-66a2-8000-885ac3f329f9 | d01e943c-7a45-f535-002f-4e2bb0d1ba12 | -4 | __resume__ | msgpack | \x9
_pregel_pull, approvalDescription
When using PostgreSQL as the checkpointer storage in LangGraph, after an interruption occurs, the checkpoint_writes table contains two forms of thread_id: one stored as a plain string and the other stored in hexadecimal-encoded form. Although they represent the same logical thread_id, when retrieving the graph state using the string-form thread_id, some of the state is missing because part of it is stored under the hexadecimal-encoded form.
System Info
System Information
OS: Windows
OS Version: 10.0.26100
Python Version: 3.11.14 | packaged by Anaconda, Inc. | (main, Oct 21 2025, 18:30:03) [MSC v.1929 64 bit (AMD64)]
Package Information
langchain_core: 1.1.2
langchain: 1.1.3
langsmith: 0.4.58
langchain_mcp_adapters: 0.1.14
langchain_openai: 1.1.1
langgraph_sdk: 0.2.14
Optional packages not installed
langserve
Other Dependencies
httpx: 0.28.1
jsonpatch: 1.33
langgraph: 1.0.4
mcp: 1.11.0
openai: 2.9.0
opentelemetry-api: 1.39.0
opentelemetry-exporter-otlp-proto-http: 1.39.0
opentelemetry-sdk: 1.39.0
orjson: 3.11.5
packaging: 24.2
pydantic: 2.11.7
pyyaml: 6.0.2
requests: 2.32.5
requests-toolbelt: 1.0.0
rich: 14.2.0
tenacity: 9.1.2
tiktoken: 0.12.0
typing-extensions: 4.15.0
uuid-utils: 0.12.0
zstandard: 0.25.