LangGraph: idempotent tool calls after interrupt, resume and crashes
LangGraph restarts a node when it resumes after interrupt(), and a crash can repeat work done after the last checkpoint. How to stop repeats.
What LangGraph does on resume
LangGraph's own documentation on interrupts says that when a graph resumes, "the runtime restarts the entire
node from the beginning", so code that ran before interrupt() runs again. Its advice: side effects
placed before an interrupt should be idempotent, or placed after it. The same idea applies to crashes: work
done after the last checkpoint is done again when the thread resumes.
Option 1: put side effects after interrupt()
def approve_and_pay(state):
decision = interrupt({"pay": state["amount"]}) # resumes here with the human's answer
if decision == "yes":
pay(state["amount"], state["to"]) # side effect AFTER the interrupt
This avoids the re-run on resume after an interrupt. It does not help if the process crashes after
pay and before LangGraph saves the node's result.
Option 2: make the tool itself once-only
Call4Agents has a LangGraph adapter: the thread id becomes the run id, so a resumed thread finds the actions it already did.
from call4agents.adapters import langgraph as c4a_lg
import call4agents as c4a
c4a.init()
@c4a_lg.effect # inside any node or tool
def send_email(to: str, body: str) -> str:
return mailer.send(to=to, body=body)
# a human approval bound to the exact arguments they saw:
@c4a_lg.effect(approval=True)
def pay(amount: int, to: str) -> str:
return payments.charge(amount=amount, to=to)
# when the interrupt is answered:
c4a_lg.approve(pay, config, amount=10, to="alice")
graph.invoke(Command(resume="yes"), config)
A repeat of the same action in the same thread returns the recorded result. If the arguments in the saved
state were changed after the "yes" (for example to pay(9999, "mallory")), the call is refused.
pip install call4agents
call4agents quickstart # offline, no API keys: 2 emails without the kit, 1 with it