LangGraph durability='sync' without a checkpointer: fixing 'SyncPregelLoop' object has no attribute '_put_checkpoint_fut'
Pass durability='sync' to a LangGraph graph compiled without a checkpointer and the run crashes after the first step. Here's why, and three small workarounds until a fix ships.
Disclosure: I work on MemorySync (I'm Mohammed, the founder). I drafted this article with AI assistance and checked it against the GitHub issue, the related PR, the LangGraph docs, the LangGraph source on PyPI and GitHub, and the MemorySync docs listed under Sources on Oct 31, 2026. Code status: I ran everything offline with outbound connections blocked and no API keys, on Python 3.12.15 with langgraph 1.2.14 (the issue's version) and 1.2.19 (the latest release when I ran it on Oct 31), both with langgraph-checkpoint 4.3.1. I ran the issue's own graph with invoke, ainvoke and stream, the Step 2 helper, the Step 3 InMemorySaver setup, and the Step 4 subgraph cases. The graphs are plain Python nodes, so no model was involved. I didn't run a Postgres or other database checkpointer, a real model, create_agent or create_react_agent, the LangGraph JS package, or the MemorySync snippet.
You passed durability="sync" to a LangGraph graph, and the run crashes right after the first node with:
AttributeError: 'SyncPregelLoop' object has no attribute '_put_checkpoint_fut'. Did you mean: '_put_checkpoint'?
With ainvoke, it's 'AsyncPregelLoop' object has no attribute '_put_checkpoint_fut' (the same wait sits in astream, which ainvoke runs through). Just before it, LangGraph prints a UserWarning: "durability has no effect when no checkpointer is present." That's LangGraph issue #9267, titled durability="sync" raises AttributeError "_put_checkpoint_fut" on a graph compiled without a checkpointer and reported on langgraph 1.2.14.
Short version: with durability="sync", LangGraph waits for each step's checkpoint save before starting the next step. When the graph has no checkpointer, no save ever starts, so there's nothing to wait on and the wait itself crashes. There's no released fix. The workaround is to pass durability only when the graph has a checkpointer (Step 2), or to give every build of the graph a checkpointer (Step 3). Subgraphs compiled with checkpointer=False need Step 4.
MemorySync doesn't fix this bug. It's in LangGraph's run loop, and the fixes below are plain LangGraph. MemorySync comes up once, near the end, as a place for long-term memory, which is a separate job from checkpoints.
Skip the long-term memory plumbing. The MemorySync LangGraph guide shows a
BaseStoreyou compile in withstore=for memory that lasts across threads. It doesn't replace your checkpointer and doesn't fix thisdurabilitybug. When you're ready, see MemorySync. Founding offer: use code FOUNDING30 for 30% off Starter for your first 3 months (Settings → Billing).
Status (Nov 14, 2026)
- #9267 was opened Oct 10, 2026 (00:03 IST) and is still open (checked Nov 14), with 4 comments and no maintainer reply. The most recent comment (Oct 29, 13:00 IST) is from another user who hit "the same
AttributeErroron_put_checkpoint_fut". - The reporter's fix, #9268 (
fix(langgraph): skip the checkpoint wait for durability="sync" without a checkpointer), was opened Oct 10 (00:15 IST) and closed by a bot 23 seconds later because the author wasn't assigned to the issue. It wasn't merged and is still closed (checked Nov 14). In an Oct 12 comment, the author says the PR is rebased and also covers subgraphs compiled withcheckpointer=False. - The latest release on PyPI is langgraph 1.2.22 (published Nov 14, 2026, 07:13 IST). I read its
pregel/main.py(a source read, not a run): it still waits onloop._put_checkpoint_futwhenever the mode is"sync", with no check for a checkpointer. I ran the reproduction on 1.2.14 and 1.2.19 only. - The checkpointer docs describe
"sync"as: "LangGraph persists changes synchronously before the next step starts." Their example call isgraph.stream({"input": "test"}, durability="sync"). The compile step isn't shown, and the mode only applies to a graph that has a checkpointer.
Step 1: Confirm it's this bug
You're looking at this bug if all of these are true:
- You pass
durability="sync"toinvoke,ainvoke,streamorastream. - The graph you call was compiled without a checkpointer (
.compile()with nocheckpointer=), or it contains a subgraph compiled withcheckpointer=False. - The traceback ends in
pregel/main.pyatloop._put_checkpoint_fut.result()(orawait cast(asyncio.Future, loop._put_checkpoint_fut)for async), with theAttributeErrorabove.
The issue's example shows a common way to end up here: the same graph is built with an optional checkpointer. In the reporter's words, "the background worker passes one so a crashed run can resume from its last step, while our CLI and unit tests build the same graph without one." Passing durability="sync" unconditionally then breaks the CLI and the tests.
Here's what I got running the issue's graph (one node that adds 1) on both versions:
| Call | langgraph 1.2.14 | langgraph 1.2.19 |
|---|---|---|
No checkpointer, invoke(..., durability="sync") |
AttributeError: 'SyncPregelLoop' ... |
same error |
No checkpointer, ainvoke(..., durability="sync") |
AttributeError: 'AsyncPregelLoop' ... |
same error |
No checkpointer, stream(..., durability="sync") |
AttributeError: 'SyncPregelLoop' ... |
same error |
No checkpointer, durability="async" or "exit" |
runs, with the warning | same |
No checkpointer, durability not passed |
runs, no warning | same |
InMemorySaver, durability="sync" |
runs | same |
Parent with InMemorySaver, subgraph compiled with checkpointer=False, durability="sync" |
AttributeError: 'SyncPregelLoop' ... |
same error |
On 1.2.14, the traceback pointed at main.py line 3055, the same line as in the issue.
Step 2: Pass durability only when there's a checkpointer
This is the reporter's own workaround ("only passing durability when graph.checkpointer is set"), as a small helper. Save it as durability_guard.py:
# durability_guard.py
from langgraph.checkpoint.base import BaseCheckpointSaver
def durability_kwargs(graph, mode="sync"):
"""Pass `durability` only when the graph was compiled with a checkpointer.
Works around langgraph issue #9267: durability="sync" on a graph compiled
without one raises AttributeError: '...PregelLoop' object has no
attribute '_put_checkpoint_fut'.
"""
if isinstance(graph.checkpointer, BaseCheckpointSaver):
return {"durability": mode}
return {}
Then spread it into your calls instead of passing durability= directly:
from durability_guard import durability_kwargs
graph = builder.compile(checkpointer=checkpointer) # checkpointer may be None
result = await graph.ainvoke(inputs, config, **durability_kwargs(graph))
# or: graph.stream(inputs, config, **durability_kwargs(graph))
With a checkpointer, this passes durability="sync" as before. Without one, it passes nothing, so the run uses the default and skips the "durability has no effect" warning too. In my runs, invoke, ainvoke and stream completed on the graph without a checkpointer, and the InMemorySaver graph still ran in "sync" mode and saved its checkpoints (steps -1, 0 and 1 for the one-node graph).
The helper only looks at the checkpointer compiled into graph. LangGraph can also hand a graph a checkpointer through the run config (that's how a parent lends one to a subgraph, per _resolve_checkpointer in the source), so call it on the root graph you compiled yourself. It doesn't help with the subgraph case; see Step 4.
Step 3: Or give the CLI and tests a checkpointer too
If you'd rather keep durability="sync" everywhere, make sure every build of the graph has a checkpointer. For a CLI or unit tests, an in-memory one is enough:
from langgraph.checkpoint.memory import InMemorySaver
def build_graph(checkpointer=None):
# Production passes a Postgres (or other) saver; CLI and tests get an in-memory one.
return builder.compile(checkpointer=checkpointer or InMemorySaver())
graph = build_graph()
graph.invoke(inputs, {"configurable": {"thread_id": "cli-1"}}, durability="sync")
Once the graph has a checkpointer, every call needs a thread_id in config["configurable"]. Without one, I got ValueError: Checkpointer requires one or more of the following 'configurable' keys: thread_id, checkpoint_ns, checkpoint_id. Per the persistence docs, InMemorySaver stores checkpoints in RAM, and "When the process restarts, all checkpoints are lost." That's usually fine for a CLI run or a test.
Step 4: Subgraphs compiled with checkpointer=False
A parent graph can have a checkpointer and still hit this bug if one of its subgraphs was compiled with checkpointer=False. The parent passes the durability mode down to the subgraph, and the subgraph has no checkpointer of its own. In my runs, the Step 2 helper didn't help here, because the parent does have a checkpointer.
Two options, both of which ran without the error on 1.2.14 and 1.2.19:
- Drop
checkpointer=Falsefrom the subgraph if it doesn't need to be stateless. Compiled with the default (None), it uses the parent's checkpointer for each call. Per the subgraph docs, that "per-invocation" default supports interrupts, whilecheckpointer=Falseis for running a subagent "like a plain function call with no checkpointing overhead". So this change means the subgraph's steps get checkpointed too. - Keep
checkpointer=Falseand run that graph withdurability="async"(the default). Per the checkpointer docs,"async"saves while the next step runs, with "a small risk that LangGraph does not write checkpoints if the process crashes during execution."
Step 5: Check it worked
- Run the call that failed (CLI command, unit test or worker job) and confirm there's no
AttributeErroron_put_checkpoint_fut. - If you used Step 2, run once without a checkpointer and confirm the "
durabilityhas no effect when no checkpointer is present" warning is gone too. - On the build that has a checkpointer, check that checkpoints are still saved:
list(graph.get_state_history(config))should list thatthread_id's checkpoints. For the one-node test graph, I got three (steps -1, 0 and 1). - Search your code for other
durability="sync"call sites, such as a second worker, a test fixture or a script, that call a graph without going through the helper.
Skip the plumbing with MemorySync
Disclosure: I build MemorySync.
This doesn't fix the durability bug, and MemorySync doesn't replace a checkpointer. The MemorySync LangGraph guide says the same: thread state ("checkpoints, resume, time travel") belongs to a checkpointer such as langgraph-checkpoint-postgres, -sqlite or -redis. MemorySync covers a different job: long-term memory across threads, sessions and surfaces. If part of what you're saving is what the agent should remember about a user next week, the guide's MemorySyncStore is a LangGraph BaseStore you compile in with store=, separate from the checkpointer and the durability setting.
From the guide:
Unverified (not run):
pip install "langchain-memorysync[langgraph]"
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)
The guide says it needs langgraph 1.2+ and Python 3.10+, with MEMORYSYNC_API_KEY in the environment (or the key passed explicitly). One note if you use the guide's persist-node example: it compiles its graph with graph.compile(), so there's no checkpointer. Adding durability="sync" to that invoke would hit this same bug. Use the Step 2 helper there too. Snippet adapted from the MemorySync LangGraph guide; 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, create an API key in the dashboard and drop the snippet into your agent.
Troubleshooting
ValueError: Checkpointer requires one or more of the following 'configurable' keys: thread_id, ...after Step 3. The graph now has a checkpointer, so every call needs{"configurable": {"thread_id": ...}}.- The same
AttributeErrorafter Step 2. Either a subgraph is compiled withcheckpointer=False(Step 4), or a call site still passesdurability="sync"directly. - The "
durabilityhas no effect when no checkpointer is present" warning, but no crash. That's"async"or"exit"on a graph without a checkpointer. In my runs it's harmless, and the Step 2 helper removes it. - Your checkpointer is Postgres and the connection drops in FastAPI. That's a different problem; see LangGraph AsyncPostgresSaver: fixing 'psycopg.OperationalError: the connection is closed' in FastAPI.
Common questions
Does this lose data?
Not in the no-checkpointer case: without a checkpointer, nothing is saved in any mode. The run just stops after the first step instead of finishing. With a checkpointer, "sync" works as documented in my runs.
Is "async" a safe stand-in for "sync"?
It depends on what you need. Per the checkpointer docs, "sync" writes every checkpoint before the next step starts, and "async" writes while the next step runs, with a small risk of missing checkpoints if the process crashes. If you need "sync", use Step 2 or Step 3 rather than switching modes.
Will a future LangGraph release fix this?
No maintainer has said. As of Nov 14 the issue is open and the fix PR was closed by a bot, not reviewed. Before removing the helper, check the release notes and run your graph without a checkpointer and with durability="sync".
Does this affect LangGraph JS? I only checked the Python package.
Related pages
- LangGraph memory explained: checkpointer vs store, and per-user memory across threads
- LangGraph AsyncPostgresSaver: fixing 'psycopg.OperationalError: the connection is closed' in FastAPI
- Persistent memory for a LangChain chatbot after LangChain 1.0
Sources
- github.com/langchain-ai/langgraph/issues/9267 (opened 2026-10-10 00:03 IST, open, label
external, 4 comments; exact error, reproduction, versions, the reporter's workaround; read via the GitHub API 2026-10-31, re-checked 2026-11-14) - github.com/langchain-ai/langgraph/pull/9268 (opened and closed by a bot 2026-10-10 00:15 IST, not merged; read 2026-10-31, re-checked 2026-11-14)
- PyPI: langgraph 1.2.14 and 1.2.19 (release times; installed
langgraph/pregel/main.pyandpregel/_loop.py);pregel/main.pyon themainbranch (read 2026-10-31); langgraph 1.2.22 release time andpregel/main.py, andmainagain (source read 2026-11-14, not run) - LangGraph docs: Checkpointers (durability modes), Subgraphs (checkpointer reference), Persistence (
InMemorySavertroubleshooting) (fetched 2026-10-31) - Offline runs (2026-10-31): the issue's graph with
invoke,ainvokeandstreamon langgraph 1.2.14 and 1.2.19; the Step 2 helper; the Step 3InMemorySaversetup; the Step 4 subgraph cases; no network, no database, no model, no keys - MemorySync LangGraph guide: docs.memorysync.io/guides/langgraph (fetched 2026-10-31)
Try it
For an overview of MemorySync, see the MemorySync homepage.
Next step: read the MemorySync LangGraph guide, then see MemorySync. Founding offer: use code FOUNDING30 for 30% off Starter for your first 3 months (Settings → Billing).
About the author
- Building MemorySync (memorysync.io) — persistent memory infrastructure for AI agents, Cursor, and Claude Code.
Comments
Join the discussionNo comments yet. Be the first to comment.
