# LangGraph durability='sync' without a checkpointer: fixing 'SyncPregelLoop' object has no attribute '_put_checkpoint_fut'

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

```text
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](https://github.com/langchain-ai/langgraph/issues/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](https://docs.memorysync.io/guides/langgraph?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_langgraph-durability-sync-no-attribute-put-checkpoint-fut) shows a `BaseStore` you compile in with `store=` for memory that lasts across threads. It doesn't replace your checkpointer and doesn't fix this `durability` bug. When you're ready, [see MemorySync](https://memorysync.io/?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_langgraph-durability-sync-no-attribute-put-checkpoint-fut). Founding offer: use code FOUNDING30 for 30% off Starter for your first 3 months (Settings → Billing).

## Status (Nov 14, 2026)

- [#9267](https://github.com/langchain-ai/langgraph/issues/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 `AttributeError` on `_put_checkpoint_fut`".
- The reporter's fix, [#9268](https://github.com/langchain-ai/langgraph/pull/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 with `checkpointer=False`.
- The latest release on PyPI is [langgraph 1.2.22](https://pypi.org/project/langgraph/) (published Nov 14, 2026, 07:13 IST). I read its `pregel/main.py` (a source read, not a run): it still waits on `loop._put_checkpoint_fut` whenever 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](https://docs.langchain.com/oss/python/langgraph/checkpointers) describe `"sync"` as: "LangGraph persists changes synchronously before the next step starts." Their example call is `graph.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:

1. You pass `durability="sync"` to `invoke`, `ainvoke`, `stream` or `astream`.
2. The graph you call was compiled without a checkpointer (`.compile()` with no `checkpointer=`), or it contains a subgraph compiled with `checkpointer=False`.
3. The traceback ends in `pregel/main.py` at `loop._put_checkpoint_fut.result()` (or `await cast(asyncio.Future, loop._put_checkpoint_fut)` for async), with the `AttributeError` above.

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

```python
# 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:

```python
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:

```python
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](https://docs.langchain.com/oss/python/langgraph/persistence), `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:

1. **Drop `checkpointer=False` from 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](https://docs.langchain.com/oss/python/langgraph/use-subgraphs), that "per-invocation" default supports interrupts, while `checkpointer=False` is for running a subagent "like a plain function call with no checkpointing overhead". So this change means the subgraph's steps get checkpointed too.
2. **Keep `checkpointer=False` and run that graph with `durability="async"`** (the default). Per the [checkpointer docs](https://docs.langchain.com/oss/python/langgraph/checkpointers), `"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

1. Run the call that failed (CLI command, unit test or worker job) and confirm there's no `AttributeError` on `_put_checkpoint_fut`.
2. If you used Step 2, run once without a checkpointer and confirm the "`durability` has no effect when no checkpointer is present" warning is gone too.
3. On the build that has a checkpointer, check that checkpoints are still saved: `list(graph.get_state_history(config))` should list that `thread_id`'s checkpoints. For the one-node test graph, I got three (steps -1, 0 and 1).
4. 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](https://docs.memorysync.io/guides/langgraph?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_langgraph-durability-sync-no-attribute-put-checkpoint-fut) 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):**

```bash
pip install "langchain-memorysync[langgraph]"
```

```python
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](https://docs.memorysync.io/guides/langgraph?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_langgraph-durability-sync-no-attribute-put-checkpoint-fut); 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](https://memorysync.io/?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_langgraph-durability-sync-no-attribute-put-checkpoint-fut), 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 `AttributeError` after Step 2.** Either a subgraph is compiled with `checkpointer=False` (Step 4), or a call site still passes `durability="sync"` directly.
- **The "`durability` has 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](https://memorysync.hashnode.dev/langgraph-asyncpostgressaver-connection-is-closed-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](https://docs.langchain.com/oss/python/langgraph/checkpointers), `"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](https://memorysync.hashnode.dev/langgraph-checkpointer-vs-store-cross-thread-memory)
- [LangGraph AsyncPostgresSaver: fixing 'psycopg.OperationalError: the connection is closed' in FastAPI](https://memorysync.hashnode.dev/langgraph-asyncpostgressaver-connection-is-closed-fastapi)
- [Persistent memory for a LangChain chatbot after LangChain 1.0](https://memorysync.hashnode.dev/langchain-persistent-memory-memorysync)

## 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.py` and `pregel/_loop.py`); `pregel/main.py` on the `main` branch (read 2026-10-31); langgraph 1.2.22 release time and `pregel/main.py`, and `main` again (source read 2026-11-14, not run)
- LangGraph docs: Checkpointers (durability modes), Subgraphs (checkpointer reference), Persistence (`InMemorySaver` troubleshooting) (fetched 2026-10-31)
- Offline runs (2026-10-31): the issue's graph with `invoke`, `ainvoke` and `stream` on langgraph 1.2.14 and 1.2.19; the Step 2 helper; the Step 3 `InMemorySaver` setup; 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](https://memorysync.io/?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_langgraph-durability-sync-no-attribute-put-checkpoint-fut).

**Next step:** read the [MemorySync LangGraph guide](https://docs.memorysync.io/guides/langgraph?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_langgraph-durability-sync-no-attribute-put-checkpoint-fut), then [see MemorySync](https://memorysync.io/?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_langgraph-durability-sync-no-attribute-put-checkpoint-fut). Founding offer: use code FOUNDING30 for 30% off Starter for your first 3 months (Settings → Billing).

