<overview>
LangGraph's human-in-the-loop patterns let you pause graph execution, surface data to users, and resume with their input:
interrupt(value)— pauses execution, surfaces a value to the callerCommand(resume=value)— resumes execution, providing the value back tointerrupt()- Checkpointer — required to save state while paused
- Thread ID — required to identify which paused execution to resume
</overview>
Requirements
Three things are required for interrupts to work:
- Checkpointer — compile with
checkpointer=InMemorySaver()(dev) orPostgresSaver(prod) - Thread ID — pass
{"configurable": {"thread_id": "..."}}to everyinvoke/streamcall - JSON-serializable payload — the value passed to
interrupt()must be JSON-serializable
Basic Interrupt + Resume
interrupt(value) pauses the graph. The value surfaces in the result under __interrupt__. Command(resume=value) resumes — the resume value becomes the return value of interrupt().
Critical: when the graph resumes, the node restarts from the beginning — all code before interrupt() re-runs.
<ex-basic-interrupt-resume>
<python>
Pause execution for human review and resume with Command.
</python>
<typescript>
Pause execution for human review and resume with Command.
</typescript>
</ex-basic-interrupt-resume>
Approval Workflow
A common pattern: interrupt to show a draft, then route based on the human's decision.
<ex-approval-workflow>
<python>
Interrupt for human review, then route to send or end based on the decision.
</python>
<typescript>
Interrupt for human review, then route to send or end based on the decision.
</typescript>
</ex-approval-workflow>
Validation Loop
Use interrupt() in a loop to validate human input and re-prompt if invalid.
<ex-validation-loop>
<python>
Validate human input in a loop, re-prompting until valid.
Each Command(resume=...) call provides the next answer. If invalid, the loop re-interrupts with a clearer message.
</python>
<typescript>
Validate human input in a loop, re-prompting until valid.
</typescript>
</ex-validation-loop>
Multiple Interrupts
When parallel branches each call interrupt(), resume all of them in a single invocation by mapping each interrupt ID to its resume value.
<ex-multiple-interrupts>
<python>
Resume multiple parallel interrupts by mapping interrupt IDs to values.
</python>
<typescript>
Resume multiple parallel interrupts by mapping interrupt IDs to values.
</typescript>
</ex-multiple-interrupts>
User-fixable errors use interrupt() to pause and collect missing data — that's the pattern covered by this skill. For the full 4-tier error handling strategy (RetryPolicy, Command error loops, etc.), see the fundamentals skill.
Side Effects Before Interrupt Must Be Idempotent
When the graph resumes, the node restarts from the beginning — ALL code before interrupt() re-runs. In subgraphs, BOTH the parent node and the subgraph node re-execute.
<idempotency-rules>
Do:
- Use upsert (not insert) operations before
interrupt() - Use check-before-create patterns
- Place side effects after
interrupt()when possible - Separate side effects into their own nodes
Don't:
- Create new records before
interrupt()— duplicates on each resume - Append to lists before
interrupt()— duplicate entries on each resume
</idempotency-rules>
<ex-idempotent-patterns>
<python>
Idempotent operations before interrupt vs non-idempotent (wrong).
</python>
<typescript>
Idempotent operations before interrupt vs non-idempotent (wrong).
</typescript>
</ex-idempotent-patterns>
<subgraph-interrupt-re-execution>
Subgraph re-execution on resume
When a subgraph contains an interrupt(), resuming re-executes BOTH the parent node (that invoked the subgraph) AND the subgraph node (that called interrupt()):
<python>
</python>
<typescript>
</typescript>
</subgraph-interrupt-re-execution>
Command(resume) Warning
Command(resume=...) is the only Command pattern intended as input to invoke()/stream(). Do NOT pass Command(update=...) as input — it resumes from the latest checkpoint and the graph appears stuck. See the fundamentals skill for the full antipattern explanation.
Fixes
<fix-checkpointer-required-for-interrupts>
<python>
Checkpointer required for interrupt functionality.
</python>
<typescript>
Checkpointer required for interrupt functionality.
</typescript>
</fix-checkpointer-required-for-interrupts>
<fix-resume-with-command>
<python>
Use Command to resume from an interrupt (regular dict restarts graph).
</python>
<typescript>
Use Command to resume from an interrupt (regular object restarts graph).
</typescript>
</fix-resume-with-command>
<boundaries>
What You Should NOT Do
- Use interrupts without a checkpointer — will fail
- Resume without the same thread_id — creates a new thread instead of resuming
- Pass
Command(update=...)as invoke input — graph appears stuck (use plain dict) - Perform non-idempotent side effects before
interrupt()— creates duplicates on resume - Assume code before
interrupt()only runs once — it re-runs every resume
</boundaries>


