resume from the same interrupt using different values #1114

Closed
opened 2026-02-20 17:43:08 -05:00 by yindo · 5 comments
Owner

Originally created by @l544301590 on GitHub (Jan 9, 2026).

Checked other resources

  • This is a bug, not a usage question. For questions, please use the LangChain Forum (https://forum.langchain.com/).
  • I added a clear and detailed title that summarizes the issue.
  • I read what a minimal reproducible example is (https://stackoverflow.com/help/minimal-reproducible-example).
  • I included a self-contained, minimal example that demonstrates the issue INCLUDING all the relevant imports. The code run AS IS to reproduce the issue.

Example Code

from typing import TypedDict
from langgraph.graph import StateGraph, END, START
from langgraph.types import Command, interrupt
from langgraph.checkpoint.memory import InMemorySaver

class State(TypedDict):
    value: int

def node(state: State) -> State:
    value = state["value"]
    allow = interrupt("Allow to add?")
    if allow:
        value += 1
    return {"value": value}

checkpointer = InMemorySaver()
graph = StateGraph(State).add_node("node", node).add_edge(START, "node").add_edge("node", END).compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "thread-1"}}

# interrupt
graph.invoke({"value": 1}, config=config)
config = graph.get_state(config).config

# resume from interrupt, first time
res = graph.invoke(Command(resume=True), config=config)
print(res["value"])

# resume from interrupt, second time
res = graph.invoke(Command(resume=False), config=config)
print(res["value"])

Error Message and Stack Trace (if applicable)

The output is 2, 2. However it is expected to be 2, 1.

Description

I resume from the same interrupt 2 times, with 2 different values. My scenario is something like this:

  • First time, the user gives a value that is allow = True. But he regrets later.
  • So Second time, the user gives a value that is allow = False. In LangGraph, branch should be forked.

However, the first resume value seems to be recorded, and the second resume value is ignored.

Different from #3275 , there is no cycle edge on the node. Two resumes are from thread_id+checkpoint_id (exactly refer to the checkpoint at where it interrupts).

System Info

System Information

OS: Darwin
OS Version: Darwin Kernel Version 25.1.0: Mon Oct 20 19:33:00 PDT 2025; root:xnu-12377.41.6~2/RELEASE_ARM64_T6020
Python Version: 3.12.12 | packaged by conda-forge | (main, Oct 22 2025, 23:34:53) [Clang 19.1.7 ]

Package Information

langchain_core: 1.2.0
langchain: 1.1.3
langchain_community: 0.4.1
langsmith: 0.3.45
langchain_classic: 1.0.0
langchain_experimental: 0.4.1
langchain_openai: 1.1.3
langchain_text_splitters: 1.1.0
langgraph_sdk: 0.3.1

Optional packages not installed

langserve

Other Dependencies

aiohttp: 3.13.2
dataclasses-json: 0.6.7
httpx: 0.28.1
httpx-sse: 0.4.3
jsonpatch: 1.33
langgraph: 1.0.5
numpy: 2.3.5
openai: 2.11.0
orjson: 3.11.5
packaging: 25.0
pydantic: 2.12.5
pydantic-settings: 2.12.0
pyyaml: 6.0.3
PyYAML: 6.0.3
requests: 2.32.5
requests-toolbelt: 1.0.0
rich: 14.2.0
SQLAlchemy: 2.0.45
sqlalchemy: 2.0.45
tenacity: 9.1.2
tiktoken: 0.9.0
typing-extensions: 4.15.0
uuid-utils: 0.12.0
zstandard: 0.23.0

Originally created by @l544301590 on GitHub (Jan 9, 2026). ### Checked other resources - [x] This is a bug, not a usage question. For questions, please use the LangChain Forum (https://forum.langchain.com/). - [x] I added a clear and detailed title that summarizes the issue. - [x] I read what a minimal reproducible example is (https://stackoverflow.com/help/minimal-reproducible-example). - [x] I included a self-contained, minimal example that demonstrates the issue INCLUDING all the relevant imports. The code run AS IS to reproduce the issue. ### Example Code ```python from typing import TypedDict from langgraph.graph import StateGraph, END, START from langgraph.types import Command, interrupt from langgraph.checkpoint.memory import InMemorySaver class State(TypedDict): value: int def node(state: State) -> State: value = state["value"] allow = interrupt("Allow to add?") if allow: value += 1 return {"value": value} checkpointer = InMemorySaver() graph = StateGraph(State).add_node("node", node).add_edge(START, "node").add_edge("node", END).compile(checkpointer=checkpointer) config = {"configurable": {"thread_id": "thread-1"}} # interrupt graph.invoke({"value": 1}, config=config) config = graph.get_state(config).config # resume from interrupt, first time res = graph.invoke(Command(resume=True), config=config) print(res["value"]) # resume from interrupt, second time res = graph.invoke(Command(resume=False), config=config) print(res["value"]) ``` ### Error Message and Stack Trace (if applicable) ```shell The output is 2, 2. However it is expected to be 2, 1. ``` ### Description I resume from the same interrupt 2 times, with 2 different values. My scenario is something like this: - First time, the user gives a value that is `allow = True`. But he regrets later. - So Second time, the user gives a value that is `allow = False`. In LangGraph, branch should be forked. However, the first resume value seems to be recorded, and the second resume value is ignored. Different from #3275 , there is no cycle edge on the node. Two `resume`s are from thread_id+checkpoint_id (exactly refer to the checkpoint at where it interrupts). ### System Info System Information ------------------ > OS: Darwin > OS Version: Darwin Kernel Version 25.1.0: Mon Oct 20 19:33:00 PDT 2025; root:xnu-12377.41.6~2/RELEASE_ARM64_T6020 > Python Version: 3.12.12 | packaged by conda-forge | (main, Oct 22 2025, 23:34:53) [Clang 19.1.7 ] Package Information ------------------- > langchain_core: 1.2.0 > langchain: 1.1.3 > langchain_community: 0.4.1 > langsmith: 0.3.45 > langchain_classic: 1.0.0 > langchain_experimental: 0.4.1 > langchain_openai: 1.1.3 > langchain_text_splitters: 1.1.0 > langgraph_sdk: 0.3.1 Optional packages not installed ------------------------------- > langserve Other Dependencies ------------------ > aiohttp: 3.13.2 > dataclasses-json: 0.6.7 > httpx: 0.28.1 > httpx-sse: 0.4.3 > jsonpatch: 1.33 > langgraph: 1.0.5 > numpy: 2.3.5 > openai: 2.11.0 > orjson: 3.11.5 > packaging: 25.0 > pydantic: 2.12.5 > pydantic-settings: 2.12.0 > pyyaml: 6.0.3 > PyYAML: 6.0.3 > requests: 2.32.5 > requests-toolbelt: 1.0.0 > rich: 14.2.0 > SQLAlchemy: 2.0.45 > sqlalchemy: 2.0.45 > tenacity: 9.1.2 > tiktoken: 0.9.0 > typing-extensions: 4.15.0 > uuid-utils: 0.12.0 > zstandard: 0.23.0
yindo added the bugpending labels 2026-02-20 17:43:08 -05:00
yindo closed this issue 2026-02-20 17:43:08 -05:00
Author
Owner

@CodeVishal-17 commented on GitHub (Jan 9, 2026):

Thanks for the clear repro.

I want to confirm intended behavior before working on a fix:
should multiple resume calls from the same interrupt but with different values create independent execution branches, or is resume intentionally treated as a single-use / cached decision per checkpoint?

If branching is intended here, I’m happy to investigate and propose a fix.

@CodeVishal-17 commented on GitHub (Jan 9, 2026): Thanks for the clear repro. I want to confirm intended behavior before working on a fix: should multiple resume calls from the same interrupt but with different values create independent execution branches, or is resume intentionally treated as a single-use / cached decision per checkpoint? If branching is intended here, I’m happy to investigate and propose a fix.
Author
Owner

@sydney-runkle commented on GitHub (Jan 9, 2026):

This is the expected behavior, you're resuming w/ the same config :)

@sydney-runkle commented on GitHub (Jan 9, 2026): This is the expected behavior, you're resuming w/ the same config :)
Author
Owner

@l544301590 commented on GitHub (Jan 11, 2026):

@CodeVishal-17 Thanks for the attention.

Thanks for the clear repro.

I want to confirm intended behavior before working on a fix: should multiple resume calls from the same interrupt but with different values create independent execution branches, or is resume intentionally treated as a single-use / cached decision per checkpoint?

If branching is intended here, I’m happy to investigate and propose a fix.

"create independent execution branches". This is a really precise description. This is exactly what I want, , especially when users regret their previous choices.

For example, in Cursor IDE, the agent generates an inline cmd code and asks for user's confirm or revise. It is a typical interrupt with resume. Although it is not implemented to allow user to revise again (and fork) in Cursor, I think it is useful.

However, since @sydney-runkle said it is an expected behavior, I wonder how to implement the above scenario, or if there is any alternative way to do this.

@l544301590 commented on GitHub (Jan 11, 2026): @CodeVishal-17 Thanks for the attention. > Thanks for the clear repro. > > I want to confirm intended behavior before working on a fix: should multiple resume calls from the same interrupt but with different values create independent execution branches, or is resume intentionally treated as a single-use / cached decision per checkpoint? > > If branching is intended here, I’m happy to investigate and propose a fix. "create independent execution branches". This is a really precise description. This is exactly what I want, , especially when users regret their previous choices. For example, in Cursor IDE, the agent generates an inline cmd code and asks for user's confirm or revise. It is a typical `interrupt` with `resume`. Although it is not implemented to allow user to revise again (and fork) in Cursor, I think it is useful. However, since @sydney-runkle said it is an expected behavior, I wonder how to implement the above scenario, or if there is any alternative way to do this.
Author
Owner

@pygenest commented on GitHub (Jan 15, 2026):

Hi @l544301590,

I ran into the same question about resuming execution with different values from the same interrupt.

After tweaking, here's a working solution (tested with langgraph==1.0.5 and langchain==1.2.1):

from typing import TypedDict
from langgraph.graph import StateGraph, END, START
from langgraph.types import Command, interrupt
from langgraph.checkpoint.memory import InMemorySaver

class State(TypedDict):
    value: int

def node(state: State) -> State:
    value = state["value"]
    allow = interrupt("Allow to add?")
    if allow:
        value += 1
    return {"value": value}

checkpointer = InMemorySaver()
graph = StateGraph(State).add_node("node", node).add_edge(START, "node").add_edge("node", END).compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "thread-1"}}

# interrupt
graph.invoke({"value": 1}, config=config)
at_interrupt_config = graph.get_state(config).config

# resume from interrupt, first time
duplicated_config = graph.update_state(at_interrupt_config, values={})
res = graph.invoke(Command(resume=True), config=duplicated_config)
print(res["value"])

# resume from interrupt, second time
duplicated_config = graph.update_state(at_interrupt_config, values={})
res = graph.invoke(Command(resume=False), config=duplicated_config)
print(res["value"])

To resume with different values, you need to duplicate the config using graph.update_state before each resume. This ensures a "clean" config and creates a new execution branch.

Warning: This does not work if the interrupt is located in a subgraph.

@pygenest commented on GitHub (Jan 15, 2026): Hi @l544301590, I ran into the same question about resuming execution with different values from the same interrupt. After tweaking, here's a working solution (tested with `langgraph==1.0.5` and `langchain==1.2.1`): ```python from typing import TypedDict from langgraph.graph import StateGraph, END, START from langgraph.types import Command, interrupt from langgraph.checkpoint.memory import InMemorySaver class State(TypedDict): value: int def node(state: State) -> State: value = state["value"] allow = interrupt("Allow to add?") if allow: value += 1 return {"value": value} checkpointer = InMemorySaver() graph = StateGraph(State).add_node("node", node).add_edge(START, "node").add_edge("node", END).compile(checkpointer=checkpointer) config = {"configurable": {"thread_id": "thread-1"}} # interrupt graph.invoke({"value": 1}, config=config) at_interrupt_config = graph.get_state(config).config # resume from interrupt, first time duplicated_config = graph.update_state(at_interrupt_config, values={}) res = graph.invoke(Command(resume=True), config=duplicated_config) print(res["value"]) # resume from interrupt, second time duplicated_config = graph.update_state(at_interrupt_config, values={}) res = graph.invoke(Command(resume=False), config=duplicated_config) print(res["value"]) ``` To resume with different values, you need to duplicate the config using `graph.update_state` before each resume. This ensures a "clean" config and creates a new execution branch. **Warning**: This does not work if the interrupt is located in a subgraph.
Author
Owner

@l544301590 commented on GitHub (Jan 22, 2026):

Hi @l544301590,

I ran into the same question about resuming execution with different values from the same interrupt.

After tweaking, here's a working solution (tested with langgraph==1.0.5 and langchain==1.2.1):

from typing import TypedDict
from langgraph.graph import StateGraph, END, START
from langgraph.types import Command, interrupt
from langgraph.checkpoint.memory import InMemorySaver

class State(TypedDict):
value: int

def node(state: State) -> State:
value = state["value"]
allow = interrupt("Allow to add?")
if allow:
value += 1
return {"value": value}

checkpointer = InMemorySaver()
graph = StateGraph(State).add_node("node", node).add_edge(START, "node").add_edge("node", END).compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "thread-1"}}

interrupt

graph.invoke({"value": 1}, config=config)
at_interrupt_config = graph.get_state(config).config

resume from interrupt, first time

duplicated_config = graph.update_state(at_interrupt_config, values={})
res = graph.invoke(Command(resume=True), config=duplicated_config)
print(res["value"])

resume from interrupt, second time

duplicated_config = graph.update_state(at_interrupt_config, values={})
res = graph.invoke(Command(resume=False), config=duplicated_config)
print(res["value"])
To resume with different values, you need to duplicate the config using graph.update_state before each resume. This ensures a "clean" config and creates a new execution branch.

Warning: This does not work if the interrupt is located in a subgraph.

This works perfectly! Thanks.

@l544301590 commented on GitHub (Jan 22, 2026): > Hi [@l544301590](https://github.com/l544301590), > > I ran into the same question about resuming execution with different values from the same interrupt. > > After tweaking, here's a working solution (tested with `langgraph==1.0.5` and `langchain==1.2.1`): > > from typing import TypedDict > from langgraph.graph import StateGraph, END, START > from langgraph.types import Command, interrupt > from langgraph.checkpoint.memory import InMemorySaver > > class State(TypedDict): > value: int > > def node(state: State) -> State: > value = state["value"] > allow = interrupt("Allow to add?") > if allow: > value += 1 > return {"value": value} > > checkpointer = InMemorySaver() > graph = StateGraph(State).add_node("node", node).add_edge(START, "node").add_edge("node", END).compile(checkpointer=checkpointer) > config = {"configurable": {"thread_id": "thread-1"}} > > # interrupt > graph.invoke({"value": 1}, config=config) > at_interrupt_config = graph.get_state(config).config > > # resume from interrupt, first time > duplicated_config = graph.update_state(at_interrupt_config, values={}) > res = graph.invoke(Command(resume=True), config=duplicated_config) > print(res["value"]) > > # resume from interrupt, second time > duplicated_config = graph.update_state(at_interrupt_config, values={}) > res = graph.invoke(Command(resume=False), config=duplicated_config) > print(res["value"]) > To resume with different values, you need to duplicate the config using `graph.update_state` before each resume. This ensures a "clean" config and creates a new execution branch. > > **Warning**: This does not work if the interrupt is located in a subgraph. This works perfectly! Thanks.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraph#1114