Hello! I want to talk about point #3 from the interrupt docs (pre-interrupt code re-runs on resume)
In practice this is biting my team a bit:
- We have
logger.info(…) calls before interrupt() and now see duplicate log lines on every resume.
- We have nodes whose pre-interrupt code is meant to run exactly once per interrupt so that a human can verify the work. To make that hold we’d have to split those nodes in two, pre-interrupt work in one node, and the interrupt + post-interrupt work in a successor. This which feels like ceremony forced by the runtime.
While digging we found a __pregel_resuming flag on get_config()[“configurable”] that’s essentially False on first invocation and True on resume. It lives in langgraph._internal._constants and also flips True on RetryPolicy replays, so it seems like a bad choice for product code.
Questions:
- Is there a public, supported way for a node to ask “am I being resumed?” or is one planned?
- Is the split-the-node approach the recommended pattern for once-per-run side effects, or is there a cleaner idiom we’re missing?
Setup: langgraph 1.1.2, Python 3.12.
Hello @Anthony ,
Not today. There’s no supported way for node code to ask “is this an interrupt resume?”
get_config()["configurable"]["__pregel_resuming"] is an internal flag ([CONFIG_KEY_RESUMING](https://github.com/keenborder786/langgraph/blob/076e2a3627206f5a1aef573aaca4a01e5af897ca/libs/langgraph/langgraph/_internal/_constants.py#L45) in langgraph._internal._constants). It’s used by the Pregel loop to decide how to apply input and propagate state to subgraphs. As you noticed, it’s also set on RetryPolicy replays (_retry.py), so it means “this tick is continuing from persisted state,” not strictly “human resumed an interrupt.” I wouldn’t rely on it in application code.
Re-running the node from the top on resume is intentional. The interrupt() docs say the graph resumes at the start of the node and re-executes all logic. interrupt() itself is the exception: on resume it returns the already-supplied value instead of raising again.
So for code in the same node as interrupt(), you should assume it runs again on every resume. That’s not an anti-pattern; it’s the contract.
For once-per-interrupt (or once-per-run) work:
A. Split nodes (StateGraph): Put setup / verification in a predecessor node, and keep interrupt() in a successor.
prepare → verify_once → ask_human (interrupt)
B. Persist idempotency in state: If you must stay in one node, write a marker to state (“verification payload already emitted”) and no-op on the second pass. Same idea as any replay-safe workflow.
C. Logging: duplicate: logger.info lines on resume are expected if logs sit before interrupt() in the same node. Practical fixes:
- Move noisy logs to a predecessor node or after you know you’re past the interrupt boundary.
- Log at the client when you handle
__interrupt__ in the stream.
- Keep node logs idempotent (same
thread_id / interrupt.id in every line) and dedupe downstream if needed.
TL;DR: No supported resume-detection flag; split the node (or use state) for run-once work, that’s the intended model, not a workaround.
Thanks @keenborder786 ,
I didn’t think to flag in the state! I like that solution, but I think we’ll go with multiple nodes to start. I appreciate the quick and awesome response! Cheers!