fix(cli): resume a checkpoint without duplicating messages or leaking the saver

- on resume, the CLI and propagate re-passed the initial state to a thread with
  an existing checkpoint; nodes do not re-run, but the message reducer appended
  the initial messages again, duplicating them in the resumed state
- feed None on resume (checkpoint_input) so LangGraph continues the interrupted
  run, and wrap the CLI stream in try/finally so the checkpointer tears down even
  if the stream raises
- correct the _fetch_returns docstring to the 4-tuple return #1249
This commit is contained in:
Yijia-Xiao
2026-08-31 01:29:14 +00:00
parent 8db41f6bca
commit b43bc31479
3 changed files with 159 additions and 105 deletions

View File

@@ -84,6 +84,38 @@ def test_begin_returns_thread_id_and_recompiles():
assert g._checkpointer_ctx is None # restored
@pytest.mark.unit
def test_checkpoint_input_is_none_only_when_resuming():
global _should_crash
with tempfile.TemporaryDirectory() as tmp:
init = {"count": 0}
args = ("AAPL", "2026-05-08", "stock")
# Fresh run: no checkpoint yet -> stream the initial state, then crash.
g1 = _bare_graph(tmp)
tid = g1.begin_checkpoint(*args)
try:
assert g1._resuming is False
assert g1.checkpoint_input(init) is init # not resuming -> initial state
_should_crash = True
with pytest.raises(RuntimeError):
for _ in g1.graph.stream(init, config={"configurable": {"thread_id": tid}}):
pass
finally:
g1.end_checkpoint()
assert g1.checkpoint_input(init) is init # reset after teardown
# A later run finds the checkpoint -> resume by feeding None, not the
# initial state (re-passing it would duplicate messages, #1249).
_should_crash = False
g2 = _bare_graph(tmp)
g2.begin_checkpoint(*args)
try:
assert g2._resuming is True
assert g2.checkpoint_input(init) is None
finally:
g2.end_checkpoint()
@pytest.mark.unit
def test_cli_style_usage_saves_then_resumes():
global _should_crash