agent_signal

Guide

LangGraph multiple interrupts: how to resume each one correctly

By Agent Signal · Published 2026-10-15 · Updated 2026-10-15

Once a graph asks more than one question, resume gets subtle: which Command(resume=...) answers which interrupt(), and why does the node run four times for three questions? Here is the behavior, run on langgraph 1.2.15 / langgraph-checkpoint 4.3.0 (Python), Oct 15, 2026.

The two rules

Tested example

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

# Part 1: three interrupts in one node, answered one resume at a time
RUNS = {"n": 0}
class Form(TypedDict, total=False):
    name: str; age: int; city: str

def ask(state: Form):
    RUNS["n"] += 1
    name = interrupt("name?"); age = interrupt("age?"); city = interrupt("city?")
    return {"name": name, "age": age, "city": city}

b = StateGraph(Form); b.add_node("ask", ask); b.add_edge(START, "ask"); b.add_edge("ask", END)
g = b.compile(checkpointer=InMemorySaver()); cfg = {"configurable": {"thread_id": "form-1"}}
out = g.invoke({}, cfg); print("pending:", out["__interrupt__"][0].value)
for v in ["Ada", 36, "London"]:
    out = g.invoke(Command(resume=v), cfg)
    print("resumed", repr(v), "->", out["__interrupt__"][0].value if "__interrupt__" in out else out)
print("node executions:", RUNS["n"])

# Part 2: two parallel nodes interrupt in the same step
class S(TypedDict):
    vals: Annotated[list[str], operator.add]
def a(s): return {"vals": [f"a:{interrupt('approve refund?')}"]}
def bb(s): return {"vals": [f"b:{interrupt('approve email?')}"]}
b = StateGraph(S); b.add_node("a", a); b.add_node("b", bb)
b.add_edge(START, "a"); b.add_edge(START, "b"); b.add_edge("a", END); b.add_edge("b", END)
g = b.compile(checkpointer=InMemorySaver()); cfg = {"configurable": {"thread_id": "par-1"}}
out = g.invoke({"vals": []}, cfg)
pending = out["__interrupt__"]; print("pending:", [i.value for i in pending])
out = g.invoke(Command(resume={i.id: ("yes" if "refund" in i.value else "no") for i in pending}), cfg)
print("final:", out)

Real output:

pending: name?
resumed 'Ada' -> age?
resumed 36 -> city?
resumed 'London' -> {'name': 'Ada', 'age': 36, 'city': 'London'}
node executions: 4
pending: ['approve email?', 'approve refund?']
final: {'vals': ['a:yes', 'b:no']}

Part 1 takes 4 node executions for 3 interrupts: the node re-runs from the top on every resume and earlier answers are replayed by position. Part 2 answers both parallel interrupts with one Command(resume={interrupt.id: value}).

Mistakes that break it

Tested starters

Free starter pack

The approval-flow starter pack

Tested human-in-the-loop approval starters for LangGraph, OpenAI Agents SDK, Vercel AI SDK and Google ADK, plus the weekly Agent Signal newsletter. Free.

Get tested agent templates weekly