# Resume Interrupts Programmatically

**URL:** <https://forum.langchain.com/t/resume-interrupts-programmatically/2541>\
**Category:** LangGraph\
**Tags:** js-help\
**Created:** [December 17, 2025, 5:39pm UTC](https://forum.langchain.com/t/resume-interrupts-programmatically/2541 "2025-12-17T17:39:37Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![LaNguAx](https://yyz1.discourse-cdn.com/flex007/user_avatar/forum.langchain.com/languax/32/1960_2.png) [@LaNguAx](https://forum.langchain.com/u/LaNguAx)\
**Post date:** [December 17, 2025, 5:39pm UTC](https://forum.langchain.com/t/resume-interrupts-programmatically/2541/1 "2025-12-17T17:39:37Z")

</div>

Hi,

I’m using LangGraph (JS/TS) with `interrupt()` for HITL-style user input. The graph pauses correctly and emits an interrupt, and I understand that the run is over at that point and must be resumed externally using `Command({ resume })`.

What I’m trying to implement is **automatic resumption after a timeout** if the user does not respond.

Conceptually, what I want is:

- Node calls `interrupt()` to request user input

- Graph run ends and state is checkpointed

- If the user responds in time → resume with user input

- If no response after X seconds → resume programmatically with a default value (e.g. `"timeout"`) – Resume the graph execution to the next node.

I understand that:

- This cannot be implemented _inside_ the graph or node itself (or maybe it can and I didn’t think of it)

- A busy-wait loop is not appropriate

- **Resumption must be done programmatically by the application layer**

My question is **what is the intended / recommended pattern for this in LangGraph?**

Specifically:

1. Is the expected approach to schedule an external timer/job (e.g. `setTimeout`, queue, cron) that calls `graph.invoke(new Command({ resume }))`? This also means I need a setInterval to check for if timeouts exist and then for n timeouts, create an interval every x seconds that validates if they should be discarded or return `graph.invoke(newCommand({resume}))`.

2. Is there a canonical way to associate a timeout with a specific interrupt (e.g. via interrupt IDs)?

3. How should race conditions be handled (user reply vs timeout firing)?

4. Is there any planned first-class support for time-based resumption, or is this intentionally left to the application layer?

I’m aiming for a scalable, multi-user setup (each `thread_id` independent), not a blocking loop per user.

Any guidance or examples from the maintainers would be greatly appreciated.

Thanks!

---

<div class="post-metadata">

**Author:** ![hntrl](https://avatars.discourse-cdn.com/v4/letter/h/ccd318/32.png) [@hntrl](https://forum.langchain.com/u/hntrl)\
**Post date:** [December 17, 2025, 6:08pm UTC](https://forum.langchain.com/t/resume-interrupts-programmatically/2541/2 "2025-12-17T18:08:17Z")

</div>

Hey! You have the right idea – LangGraph leaves scheduling/timing concerns to the application layer. Part of the architecture solve that LangGraph provides is that graph states can be stored and re-established from any process. If we became opinionated about scheduling, that means we have additional opinions on how your infrastructure/workers should look like and we (probably) don’t have enough value that we can provide there meaningfully.

> Is the expected approach to schedule an external timer/job (e.g. `setTimeout`, queue, cron) that calls `graph.invoke(new Command({ resume }))`?

Yes! This is a pattern we’ve seen before that works well

> This also means I need a setInterval to check for if timeouts exist and then for n timeouts

Any reason you couldn’t have a setTimeout that invokes with a new command?

> Is there a canonical way to associate a timeout with a specific interrupt (e.g. via interrupt IDs)?

No, think that would just be solved by the nature of the callback you give to setTimeout

> How should race conditions be handled (user reply vs timeout firing)?

This is a user-land concern also I believe. You can invoke a thread concurrently, but we take the attitude of “last-write wins” when evaluating what the final thread state should be

> Is there any planned first-class support for time-based resumption, or is this intentionally left to the application layer?

No immediate plans

---

<div class="post-metadata">

**Author:** ![LaNguAx](https://yyz1.discourse-cdn.com/flex007/user_avatar/forum.langchain.com/languax/32/1960_2.png) [@LaNguAx](https://forum.langchain.com/u/LaNguAx)\
**Post date:** [December 17, 2025, 11:15pm UTC](https://forum.langchain.com/t/resume-interrupts-programmatically/2541/3 "2025-12-17T23:15:54Z")

</div>

Thank you for the assistance 🙂

Edit:

Figured I’d post a code solution if anyone encounters a similiar problem. For a graph without loops, invoking the graph, awaiting response and if it returned an ` __interrupt__ ` field then using a simple timeout with a resume command callback will work to programmatically resume the interrupt.  
However, for a graph with loops (start\_node→…→interrupt\_hitl\_node→…→start\_node→…) it’s a bit more complicated to handle interrupts iteratively (and more specifically triggering a timeout for them).  
The solution I came up with involved running a `while` loop, it allowed to keep the graph running and whenever `interrupts` were raised handle them in `switch` cases.  
Maybe there are better ways to handle interrupts in a graph with loops, if someone has feedback it’ll be appreciated.

**Main Code:**

```auto
type InterruptValue = { reason: string; [k: string]: unknown };
async function runGraph(state: ArtistGraphState = initialState, config: Config) {
  // Keep invoking until the graph finishes (i.e., no interrupt is returned).
  let run = await graph.invoke(state, config);

  while (isInterrupted<InterruptValue>(run)) {
    const interrupt = run[INTERRUPT][0];

    // handle interrupts here
    switch (interrupt.value?.reason) {
      case 'getUserInput': {
        const userInput: 'timeout' | string = await handleGetUserInputInterrupt();

        // If userInput==timeout then invoke with a timeout command and handle it in node..
        run = await graph.invoke(new Command({ resume: userInput }), config);
        break;
      }

      // Add more cases here for other interrupts
      case 'placeholderInterruptName': {
        // perform logic
        // ...
        // ...
        run = await graph.invoke(new Command({ resume: 'ValueToResumeWith' }), config);
        break;
      }

      default:
        return run;
    }
  }

  return run;
}

const run = await runGraph(initialState, config);

```

**Specific example code & utils:**

```auto
async function handleGetUserInputInterrupt(): Promise<'timeout' | string> {

  const controller = new AbortController();

  let timeout: ReturnType<typeof setTimeout> | undefined;

  const timeoutPromise = new Promise<'timeout'>((resolve) => {

    timeout = setTimeout(() => {

      controller.abort();

      resolve('timeout');

    }, 25_000);

  });

  
  try {

    // gets user input using readline interface, returns Promise<string>

    const userInput = getUserInput('Please enter your input:', controller);

    

    const result = await Promise.race<'timeout' | string>([timeoutPromise, userInput]);

    

    return result;

  } catch (error) {

    if (error instanceof Error && error.message === 'Abort') return 'timeout';

    

    throw error;

  } finally {

    if (timeout) clearTimeout(timeout);

  }

}

// Utility function for getting user input

export function getUserInput(prompt: string = '', controller: AbortController): Promise<string> {

  const rl = readline.createInterface({

    input: process.stdin,

    output: process.stdout,

  });

  return new Promise((resolve, reject) => {

    let settled = false;

    function cleanup() {

      controller.signal.removeEventListener('abort', onAbort);

      readline.clearLine(process.stdout, 0);

      readline.cursorTo(process.stdout, 0);

      rl.close();

    }

    function onAbort() {

      if (settled) return;

      settled = true;

      cleanup();

      reject(new Error('Abort'));

    }

    controller.signal.addEventListener('abort', onAbort, { once: true });

    rl.question(prompt, (answer) => {

      if (settled) return;

      settled = true;

      cleanup();

      resolve(answer.trim());

    });

  });

}

```
