DEV Community

Vaibhav Tekam
Vaibhav Tekam

Posted on

We Built a Fix for a LangGraph Gotcha That's Cost Multiple Teams Real Debugging Time. Then Almost Shipped Three Bugs of Our Own

Summer Bug Smash: Smash Stories 🐛🛹

This is a submission for DEV's Summer Bug Smash: Smash Stories powered by Sentry.

langgraph dev has a quirk that isn't obvious until it costs you real time: it silently ignores whatever checkpointer you configured and forces in-memory storage instead. Restart the process, hot-reload during development, and your agent's entire conversation state is just gone, no error, no warning, the checkpoint file gets created and just stays empty. It's documented as a real, reproducible bug (GitHub issue #5790), and it isn't isolated. A related architectural gap around task-level checkpointing under the API server runtime shows up again in issue #6559, written up independently in real technical depth by a developer who lost three days to it, and again in issue #5360.

A Reddit thread from earlier this year makes the actual stakes plain. Someone hosting a deep agent on LangSmith's free tier hit the same wall trying to configure Postgres for persistence, got told dev mode doesn't support a custom checkpointer, and asked outright whether they needed to buy a license to fix it. Another person replied that they'd tried the documented environment-variable workaround too, and it wasn't working either. The thread ends with both of them seriously considering abandoning the managed dev server entirely and hand-building a FastAPI wrapper instead, just to get persistence to behave.

That's a real, recurring pain point, not a one-off, and it's worth being current about where LangGraph stands on it today. They've since shipped an official answer: you can now register a fully custom checkpointer through a checkpointer key in langgraph.json, validated against a conformance test suite before the server trusts it. It's a genuine fix, and credit where it's due. It's also explicitly marked alpha, "may experience breaking changes in minor version updates," and it asks for real integration work, five required operations implemented and conformance-tested, a config file wired up, before it runs, meaningfully more setup than the simple compile(checkpointer=...) pattern most people reach for first, the same pattern that was silently ignored in every report above. For anyone not yet on that alpha path, or building across more than one agent framework, the gap those reports describe is still exactly the one you'll hit.

So we built Checkpnt, a framework-agnostic checkpoint layer, one API, SQLite for dev, Redis for prod, resume any agent from exactly where it stopped, no conformance suite or alpha flag required, and it isn't tied to any single framework's persistence model at all.

Then, going through it properly before anyone else touched it, we found three bugs that would have made our own launch a worse story than the one we were fixing.

The one that actually worried me

The resume path had never been tested end to end.

I want to be honest about how that happened, because it's a more common mistake than it should be. We'd built the write side carefully, every checkpoint immutable, checksummed, parent-linked, a clean audit trail on paper. What we hadn't actually proven was that you could take one of those checkpoints and get a real agent back to the exact state it left off in. We had a checkpoint writer. We didn't yet have proof we had a checkpoint reader.

The honest line from that week: if the resume path doesn't work, we don't have a product, we have a very well-organized way of writing data nobody can use. Everything else about the project could be defended or deferred. That one couldn't.

The one that would have been the worst kind of bug

Deleting a checkpoint removed its payload from Redis but left a stale reference behind in the index. The next time you called history(), it would fetch, say, ten IDs from that index, quietly discover three of the payloads no longer existed, filter them out, and hand back seven results, with absolutely nothing telling you that three were missing.

For a tool whose entire reason to exist is being a trustworthy audit trail, a silent gap like that is close to the worst failure mode available. Not a crash. Not an error. Just quietly less data than you asked for, and no way to know unless you happened to count.

The one we'd already half-admitted to ourselves

Somewhere in the code was a comment next to the ID generator, roughly: "these aren't real UUID v7s yet, fix when Python 3.13 is baseline." Python 3.13 was already out. We'd left the comment in and shipped the fake IDs anyway, format looked right, spec compliance didn't hold up if anything downstream actually validated it.

That one stung a little more than the other two, honestly, because we'd already caught it ourselves and just hadn't gotten around to fixing it. There's a difference between a bug you didn't see and a bug you wrote a comment about and moved past.

What actually held up

We fixed the resume path first, since nothing else mattered if that stayed broken. Then the silent Redis gap, giving history() an honest count instead of a quietly wrong one. Then the UUID generator, actually replacing the placeholder instead of just narrating that we would eventually.

Checkpnt is live and open source now. A single organic mention on GitHub, no promotion behind it, pulled 45 real clones before we'd even written a proper announcement, which told us the original problem was exactly as real as three separate GitHub issues and a Reddit thread full of people asking whether they needed to pay for a license just to keep their agent's memory intact suggested it was.

Curious whether others have shipped something and only found the real bug after a fix that was supposed to be simple turned out to expose something bigger underneath it. What was the one that actually worried you?

Repo: github.com/Tech4Biz-Solutions-Pvt-Ltd/checkpnt

Top comments (0)