mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-10-20 06:51:41 +02:00
A `DeltaChannel` with no version at a checkpoint was never written, so
its value is empty. `channels_from_checkpoint` still asked the saver for
its history, and with no snapshot to stop at, the walk reads every
ancestor of the thread, on every run and every `get_state`. A channel
that never gets written is common: deepagents' `files` channel when an
agent doesn't use files.
On SQLite the walk fails outright once the thread is long enough,
because stage 2 binds one variable per ancestor. With one never-written
channel on a 33,000-step thread:
```
main: get_state -> OperationalError: too many SQL variables
next run -> OperationalError: too many SQL variables
branch: get_state ok in 1 ms, next run ok
```
## Fix
Only channels that have a version at the checkpoint are walked. A
channel without one hydrates empty without asking the saver.
A version is assigned whenever a channel is written (by a task, an
input, `update_state` or a migration from a plain channel), so no
version means no write on this branch. The walk could only have found
writes this branch never applied.
Snapshotting such a channel instead would also bound the walk, but it
writes a blob for an empty value on every thread. Today
`create_checkpoint` skips any channel without a version, so even the
forced `DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT` snapshot never lands for
it, and the walk has no bound.
## Tests
`test_delta_channel_supersteps_bound.py`: a graph whose second delta
channel is never written asks the saver only for the written channel's
history, sync and async. Both fail on main (`asked for [['a', 'b'],
['a', 'b']]`). A third test covers the case the skip must not break: a
delta channel's first write, left pending on an interrupted head before
the channel has a version, lands exactly once on resume. It passes on
main too.
JS twin: langchain-ai/langgraphjs#2927.