fix(api): run.start on a cancelled thread must not fold input into a resume - #2751
Conversation
🦋 Changeset detectedLatest commit: 271d965 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
@langchain/langgraph-checkpoint
@langchain/langgraph-checkpoint-mongodb
@langchain/langgraph-checkpoint-postgres
@langchain/langgraph-checkpoint-redis
@langchain/langgraph-checkpoint-sqlite
@langchain/langgraph-checkpoint-validation
create-langgraph
@langchain/langgraph-api
@langchain/langgraph-cli
@langchain/langgraph
@langchain/langgraph-cua
@langchain/langgraph-supervisor
@langchain/langgraph-swarm
@langchain/langgraph-ui
@langchain/langgraph-sdk
@langchain/angular
@langchain/react
@langchain/svelte
@langchain/vue
commit: |
…resume A cancelled run ends with status "interrupted" — the same status as a genuine interrupt() pause — but has no pending interrupt to consume a resume value. createOrResumeRun treated status alone as sufficient, so a message submitted after stop() was folded into Command(resume) that no node ever reads and silently dropped, while the interrupted node re-ran with stale state. The input-vs-resume decision now keys on hasPendingInterruptsForThread (already computed in the same scope): a thread paused at interrupt() still resumes; a cancelled thread gets plain input, which starts a fresh run from the checkpoint and applies the message to state. Applied to both copies of the condition (protocol service and embed protocol) with a regression test that is red on main for the cancel case. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
e6a5f12 to
f9ad197
Compare
|
The failing |
Christian Bromann (christian-bromann)
left a comment
There was a problem hiding this comment.
LGTM 👍
0a9611a
into
langchain-ai:main
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ `main` is currently in **pre mode** so this branch has prereleases rather than normal releases. If you want to exit prereleases, run `changeset pre exit` on `main`.⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ # Releases ## @langchain/langgraph-api@1.4.6-rc.0 ### Patch Changes - [#2751](#2751) [`0a9611a`](0a9611a) Thanks [@t3s7r](https://github.com/t3s7r)! - fix(api): `run.start` with input on a cancelled thread no longer folds the input into `Command(resume)`. A cancelled run shares the "interrupted" status with a genuine `interrupt()` pause, but has no pending interrupt to consume the resume value, so the submitted message was silently dropped. The input-vs-resume decision now keys on whether the thread actually has pending interrupts (both in the protocol service and the embed protocol). - Updated dependencies [[`83a4b62`](83a4b62), [`2fab6fd`](2fab6fd)]: - @langchain/langgraph@1.4.15-rc.0 - @langchain/langgraph-sdk@1.10.3-rc.0 - @langchain/langgraph-ui@1.4.6-rc.0 ## @langchain/langgraph-cli@1.4.6-rc.0 ### Patch Changes - Updated dependencies [[`0a9611a`](0a9611a)]: - @langchain/langgraph-api@1.4.6-rc.0 ## @langchain/langgraph@1.4.15-rc.0 ### Patch Changes - [#2794](#2794) [`83a4b62`](83a4b62) Thanks [@hntrl](https://github.com/hntrl)! - feat(langgraph): add per-node `tracePolicy` input/output processors and `omitPayload` Transform the payloads recorded on a node's own trace run while retaining its span and timing. Processors receive raw values and fall back to the original payload if they throw. Graph state, root runs, and child runs remain unchanged when processors do not mutate their arguments. Matches Python's callback-level behavior: transforms also affect chain events and message streaming, so omitting outputs can suppress messages returned directly by nodes and omitting inputs can affect message deduplication. - Updated dependencies [[`2fab6fd`](2fab6fd)]: - @langchain/langgraph-sdk@1.10.3-rc.0 ## @langchain/langgraph-cua@1.0.3-rc.0 ### Patch Changes - Updated dependencies [[`83a4b62`](83a4b62)]: - @langchain/langgraph@1.4.15-rc.0 ## @langchain/langgraph-supervisor@1.1.2-rc.0 ### Patch Changes - Updated dependencies [[`83a4b62`](83a4b62)]: - @langchain/langgraph@1.4.15-rc.0 ## @langchain/langgraph-swarm@1.0.3-rc.0 ### Patch Changes - Updated dependencies [[`83a4b62`](83a4b62)]: - @langchain/langgraph@1.4.15-rc.0 ## @langchain/langgraph-sdk@1.10.3-rc.0 ### Patch Changes - [#2762](#2762) [`2fab6fd`](2fab6fd) Thanks [@JessYanCoding](https://github.com/JessYanCoding)! - Send `checkpoint_id` in the `runs.stream()` request body, so a `checkpointId` passed to `client.runs.stream()` forks from the requested checkpoint instead of being silently dropped. Matches `runs.create()` and `runs.wait()`, which already send it. ## @langchain/angular@1.0.36-rc.0 ### Patch Changes - Updated dependencies [[`2fab6fd`](2fab6fd)]: - @langchain/langgraph-sdk@1.10.3-rc.0 ## @langchain/react@1.0.36-rc.0 ### Patch Changes - Updated dependencies [[`2fab6fd`](2fab6fd)]: - @langchain/langgraph-sdk@1.10.3-rc.0 ## @langchain/svelte@1.0.36-rc.0 ### Patch Changes - Updated dependencies [[`2fab6fd`](2fab6fd)]: - @langchain/langgraph-sdk@1.10.3-rc.0 ## @langchain/vue@1.0.36-rc.0 ### Patch Changes - Updated dependencies [[`2fab6fd`](2fab6fd)]: - @langchain/langgraph-sdk@1.10.3-rc.0 ## @langchain/langgraph-ui@1.4.6-rc.0 No changes in this release. --------- Signed-off-by: Hunter Lovell <40191806+hntrl@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Hunter Lovell <hunter@hntrl.io> Co-authored-by: Hunter Lovell <40191806+hntrl@users.noreply.github.com>
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases ## @langchain/langgraph-sdk@1.11.0 ### Minor Changes - [#2831](#2831) [`3234c69`](3234c69) Thanks [@ramon-langchain](https://github.com/ramon-langchain)! - Expose connection lifecycle callbacks for built-in streaming transports. `onConnected` runs after the initial SSE or WebSocket connection becomes usable and after every successful reconnect. Its payload distinguishes an `initial` connection from a `reconnected` connection and includes the reconnect attempt number. `onReconnect` now also receives the scheduled `delayMs`, allowing applications to display accurate retry state before the next connection attempt. React, Vue, Svelte, and Angular stream hooks forward both callbacks. ### Patch Changes - [#2831](#2831) [`3234c69`](3234c69) Thanks [@ramon-langchain](https://github.com/ramon-langchain)! - Recover cleanly when a thread's root event stream terminates unexpectedly. If the stream fails or closes while a run is active, the controller now records the transport error and clears `isLoading` rather than leaving the UI in a permanently running state. Once the failed pump settles, a later submission can start a fresh root subscription without recreating the thread stream or replaying the failed command. - [#2809](#2809) [`11a4535`](11a4535) Thanks [@hntrl](https://github.com/hntrl)! - fix(sdk): appropriately track persisted seq for stream replay Sequences weren't being appropriately attributed when rehydrating the page (e.g. on refresh). This meant we'd lose stream information on `useStream` on reloads. This has been fixed by adding a lookup step to determine what the most appropriate sequence index is to track in the event stream.:x - [#2762](#2762) [`2fab6fd`](2fab6fd) Thanks [@JessYanCoding](https://github.com/JessYanCoding)! - Send `checkpoint_id` in the `runs.stream()` request body, so a `checkpointId` passed to `client.runs.stream()` forks from the requested checkpoint instead of being silently dropped. Matches `runs.create()` and `runs.wait()`, which already send it. - [#2813](#2813) [`4fc118f`](4fc118f) Thanks [@eliornl](https://github.com/eliornl)! - fix(sdk): show interrupts raised after a passive thread rejoin After a page refresh mid-run, `useStream` filtered every interrupt it did not already know from the hydrated thread state as replayed history, and waited for a `checkpoints` event to lift that filter. Current runtimes never emit that event and the replay buffer trims it on long runs, so interrupts raised after the refresh never appeared until the next reload. Unknown interrupts are now settled against the server's thread state when the run reaches a terminal lifecycle: the ones the server lists as pending are shown, the rest are dropped as history. - [#2812](#2812) [`db4bdad`](db4bdad) Thanks [@eliornl](https://github.com/eliornl)! - fix(sdk): recover from a server-side thread stream drop instead of freezing The protocol SSE transport now reconnects when the server closes the event stream cleanly. The thread stream is open-ended, so a clean close only happens when the server's own upstream consumer died or it is restarting; before, the client treated it as the end of the thread and the UI froze mid-run with no error. A connection that delivered events also resets the reconnect budget, so long-lived pages survive repeated deploys. `maxReconnectAttempts: 0` keeps the old end-on-close behavior. Unsolicited server error frames (no command id) and a shared stream that gives up reconnecting now reach `stream.error`: `ThreadStream.onError` exposes them, `useStream` sets `error`, clears `isLoading`, and settles the in-flight `submit()` as failed. - [#2808](#2808) [`55fa26b`](55fa26b) Thanks [@hntrl](https://github.com/hntrl)! - fix(sdk): coalesce locally resolved interrupts when resolving interrupts using `useStream`, there was a case where we prioritized the remote state values (which we lookup in React Strict mode on every page transition) over the local interrupt responses we know we've responded with. This has since been fixed to first prioritize the local cache of interrupts, resolved against the remote state values when a run hits a terminal event ## @langchain/angular@1.1.0 ### Minor Changes - [#2831](#2831) [`3234c69`](3234c69) Thanks [@ramon-langchain](https://github.com/ramon-langchain)! - Expose connection lifecycle callbacks for built-in streaming transports. `onConnected` runs after the initial SSE or WebSocket connection becomes usable and after every successful reconnect. Its payload distinguishes an `initial` connection from a `reconnected` connection and includes the reconnect attempt number. `onReconnect` now also receives the scheduled `delayMs`, allowing applications to display accurate retry state before the next connection attempt. React, Vue, Svelte, and Angular stream hooks forward both callbacks. ### Patch Changes - Updated dependencies [[`3234c69`](3234c69), [`3234c69`](3234c69), [`11a4535`](11a4535), [`2fab6fd`](2fab6fd), [`4fc118f`](4fc118f), [`db4bdad`](db4bdad), [`55fa26b`](55fa26b)]: - @langchain/langgraph-sdk@1.11.0 ## @langchain/react@1.1.0 ### Minor Changes - [#2831](#2831) [`3234c69`](3234c69) Thanks [@ramon-langchain](https://github.com/ramon-langchain)! - Expose connection lifecycle callbacks for built-in streaming transports. `onConnected` runs after the initial SSE or WebSocket connection becomes usable and after every successful reconnect. Its payload distinguishes an `initial` connection from a `reconnected` connection and includes the reconnect attempt number. `onReconnect` now also receives the scheduled `delayMs`, allowing applications to display accurate retry state before the next connection attempt. React, Vue, Svelte, and Angular stream hooks forward both callbacks. ### Patch Changes - Updated dependencies [[`3234c69`](3234c69), [`3234c69`](3234c69), [`11a4535`](11a4535), [`2fab6fd`](2fab6fd), [`4fc118f`](4fc118f), [`db4bdad`](db4bdad), [`55fa26b`](55fa26b)]: - @langchain/langgraph-sdk@1.11.0 ## @langchain/svelte@1.1.0 ### Minor Changes - [#2831](#2831) [`3234c69`](3234c69) Thanks [@ramon-langchain](https://github.com/ramon-langchain)! - Expose connection lifecycle callbacks for built-in streaming transports. `onConnected` runs after the initial SSE or WebSocket connection becomes usable and after every successful reconnect. Its payload distinguishes an `initial` connection from a `reconnected` connection and includes the reconnect attempt number. `onReconnect` now also receives the scheduled `delayMs`, allowing applications to display accurate retry state before the next connection attempt. React, Vue, Svelte, and Angular stream hooks forward both callbacks. ### Patch Changes - Updated dependencies [[`3234c69`](3234c69), [`3234c69`](3234c69), [`11a4535`](11a4535), [`2fab6fd`](2fab6fd), [`4fc118f`](4fc118f), [`db4bdad`](db4bdad), [`55fa26b`](55fa26b)]: - @langchain/langgraph-sdk@1.11.0 ## @langchain/vue@1.1.0 ### Minor Changes - [#2831](#2831) [`3234c69`](3234c69) Thanks [@ramon-langchain](https://github.com/ramon-langchain)! - Expose connection lifecycle callbacks for built-in streaming transports. `onConnected` runs after the initial SSE or WebSocket connection becomes usable and after every successful reconnect. Its payload distinguishes an `initial` connection from a `reconnected` connection and includes the reconnect attempt number. `onReconnect` now also receives the scheduled `delayMs`, allowing applications to display accurate retry state before the next connection attempt. React, Vue, Svelte, and Angular stream hooks forward both callbacks. ### Patch Changes - Updated dependencies [[`3234c69`](3234c69), [`3234c69`](3234c69), [`11a4535`](11a4535), [`2fab6fd`](2fab6fd), [`4fc118f`](4fc118f), [`db4bdad`](db4bdad), [`55fa26b`](55fa26b)]: - @langchain/langgraph-sdk@1.11.0 ## @langchain/langgraph-api@1.4.6 ### Patch Changes - [#2751](#2751) [`0a9611a`](0a9611a) Thanks [@t3s7r](https://github.com/t3s7r)! - fix(api): `run.start` with input on a cancelled thread no longer folds the input into `Command(resume)`. A cancelled run shares the "interrupted" status with a genuine `interrupt()` pause, but has no pending interrupt to consume the resume value, so the submitted message was silently dropped. The input-vs-resume decision now keys on whether the thread actually has pending interrupts (both in the protocol service and the embed protocol). - Updated dependencies []: - @langchain/langgraph-ui@1.4.6 ## @langchain/langgraph-cli@1.4.6 ### Patch Changes - Updated dependencies [[`0a9611a`](0a9611a)]: - @langchain/langgraph-api@1.4.6 ## @langchain/langgraph@1.4.15 ### Patch Changes - [#2794](#2794) [`83a4b62`](83a4b62) Thanks [@hntrl](https://github.com/hntrl)! - feat(langgraph): add per-node `tracePolicy` input/output processors and `omitPayload` Transform the payloads recorded on a node's own trace run while retaining its span and timing. Processors receive raw values and fall back to the original payload if they throw. Graph state, root runs, and child runs remain unchanged when processors do not mutate their arguments. Matches Python's callback-level behavior: transforms also affect chain events and message streaming, so omitting outputs can suppress messages returned directly by nodes and omitting inputs can affect message deduplication. - Updated dependencies [[`3234c69`](3234c69), [`3234c69`](3234c69), [`11a4535`](11a4535), [`2fab6fd`](2fab6fd), [`4fc118f`](4fc118f), [`db4bdad`](db4bdad), [`55fa26b`](55fa26b)]: - @langchain/langgraph-sdk@1.11.0 ## @langchain/langgraph-cua@1.0.3 No changes in this release. ## @langchain/langgraph-supervisor@1.1.2 No changes in this release. ## @langchain/langgraph-swarm@1.0.3 No changes in this release. ## @langchain/langgraph-ui@1.4.6 No changes in this release. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Problem
createOrResumeRundecides input-vs-resume like this:A cancelled run also ends with status
"interrupted"— the same status a genuineinterrupt()pause has — but the thread has no pending interrupt to consume a resume value. LangGraph only delivers resume values to aninterrupt()call, so when a client stops a run (stop()→runs.cancel) and then sends a follow-up message, that message is folded intoCommand(resume: input)that nothing reads: the message is silently dropped and the interrupted node re-runs with stale state.Reproduction (SDK
useStreamagainstlanggraphjs dev): start a run on a graph with a long-running node,stop()mid-node, thensubmit({ messages: [...] }). The created run hasinput: nullandcommand.resumecarrying the message; the thread's final state contains no trace of it.Fix
Key the decision on
hasPendingInterruptsForThreadalone (already computed in the same scope; it reads thread-state tasks withsubgraphs: true):interrupt()→ still resumes (unchanged),Applied to both copies of the condition:
protocol/service.mtsandexperimental/embed/protocol.mts.Test
tests/protocol-v2/run-start-cancelled.test.mtsdrivesProtocolService.handleCommand("run.start")with stub bindings: the cancel case (interrupted status, no pending interrupts) must submit input — red on current main; the pending-interrupt case must still produceCommand(resume)— green before and after.pnpm typecheckclean; the rest of the suite unchanged (the threefetch failedfiles fail identically on clean main in my sandbox).🤖 Generated with Claude Code