fix(langgraph-checkpoint-postgres): mint unique channel versions to prevent sibling-branch blob loss - #2584
Open
Nitsan Cohen (NitsanCohen770) wants to merge 5 commits into
Conversation
…ling-branch blob loss PostgresSaver inherits the base integer getNextVersion (1, 2, 3, ...). Channel values are stored in checkpoint_blobs keyed by (thread_id, checkpoint_ns, channel, version) and inserted with ON CONFLICT DO NOTHING. When a thread is branched via a time-travel fork (updateState with a checkpoint_id), two branches off the same base advance a channel to the SAME integer version, so the second branch's blob write is silently dropped and later reads of that branch return the first branch's value. Override getNextVersion in PostgresSaver to mint globally-unique, lexically-ordered version strings of the form `<zero-padded counter>.<uuid>`, mirroring the unique-version scheme the Python saver uses. The counter prefix preserves ordering (versions are compared as strings via localeCompare); the uuid suffix makes sibling branches write distinct checkpoint_blobs keys, so neither write is dropped. The version column is already TEXT, so no migration is required. Adds a unit test for getNextVersion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
… internally) Per review feedback: the output of getNextVersion is always a string, so `BaseCheckpointSaver<string>` is the accurate type parameter. The wider `string | number` input is kept on the method parameter to accept legacy integer versions read from existing checkpoints. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@langchain/langgraph-checkpoint
@langchain/langgraph-checkpoint-mongodb
@langchain/langgraph-checkpoint-postgres
@langchain/langgraph-checkpoint-redis
@langchain/langgraph-checkpoint-sqlite
@langchain/langgraph-checkpoint-validation
create-langgraph
@langchain/langgraph-api
@langchain/langgraph-cli
@langchain/langgraph
@langchain/langgraph-cua
@langchain/langgraph-supervisor
@langchain/langgraph-swarm
@langchain/langgraph-ui
@langchain/langgraph-sdk
@langchain/angular
@langchain/react
@langchain/svelte
@langchain/vue
commit: |
Declaring the class as `BaseCheckpointSaver<string>` made getNextVersion
return `string`, so PostgresSaver was no longer assignable to
`BaseCheckpointSaver<number>` — which every graph `compile({ checkpointer })`
call site expects (this broke langgraph-core's postgres integration tests
with `Type 'string' is not assignable to type 'number'`).
Revert to the default (`number`) generic and keep the unique, lexically
ordered version string at runtime, casting the return. PostgresSaver stays
a drop-in `BaseCheckpointSaver<number>`; channel versions are
`string | number` on the wire, so the stored string is a valid version.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Nitsan Cohen (NitsanCohen770)
marked this pull request as ready for review
July 2, 2026 21:29
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
PostgresSaverinherits the base integergetNextVersion(1, 2, 3, …). Channel values are stored incheckpoint_blobskeyed by(thread_id, checkpoint_ns, channel, version)and inserted withON CONFLICT DO NOTHING:When a thread is branched with a time-travel fork (
updateStatewith acheckpoint_id, i.e. two versions built from the same base checkpoint), both branches run the same number of super-steps and therefore advance a channel to the same integer version. The two branches write different blobs to the same primary key, so the second branch's write is silently dropped, and any later read of that branch loads the first branch's value.Net effect: a version forked from the same base as a sibling inherits the sibling's channel data (files, messages, …) on a re-read. It only surfaces on a read in a later super-step, so single-turn tests don't catch it.
The Python
PostgresSaverdoesn't have this bug because it mints unique, ordered version strings; the JS port kept the base integer scheme.Fix
Override
getNextVersioninPostgresSaverto return globally-unique, lexically-ordered version strings of the form<zero-padded counter>.<uuid>:localeCompare, so equal-width padding keeps lexical order == numeric order);uuidsuffix makes two sibling branches at the same logical counter write distinctcheckpoint_blobskeys, so neither write is dropped.getNextVersionis the single documented seam for custom version schemes, theversioncolumn is alreadyTEXT, and reads joincheckpoint.channel_versionsagainstcheckpoint_blobs.version— so no other method and no migration is needed.Test
Adds a unit test for
getNextVersion(start value, increment from numeric and prior-string versions, per-call uniqueness, and lexical == numeric ordering).Verification
Reproduced end-to-end against a real Postgres with a deep agent that forks one thread into two branches: on the stock
PostgresSaverthe second branch's re-read inherits the first branch's file; with this change each branch keeps its own. ThegetNextVersionproperties above were verified directly against the edited source.