Can not stop sub graph when asyncio.CancelledError occurred #855

Open
opened 2026-02-20 17:42:07 -05:00 by yindo · 7 comments
Owner

Originally created by @mahonzhan on GitHub (Jul 28, 2025).

Originally assigned to: @casparb on GitHub.

Checked other resources

  • This is a bug, not a usage question. For questions, please use the LangChain Forum (https://forum.langchain.com/).
  • I added a clear and detailed title that summarizes the issue.
  • I read what a minimal reproducible example is (https://stackoverflow.com/help/minimal-reproducible-example).
  • I included a self-contained, minimal example that demonstrates the issue INCLUDING all the relevant imports. The code run AS IS to reproduce the issue.

Example Code

async def call_subgraph(state, config):
    output = await subgraph.ainvoke(
        input=state,
        config=config,
    )
    messages = output["messages"]
    messages = messages[-1:]
    return {
        **output,
        "messages": messages,
    }

parent_graph.add_node(node="call_subgraph", action=call_subgraph)

Error Message and Stack Trace (if applicable)


Description

I use fastapi and graph.astream to provide event stream, when I cancel request, asyncio.CancelledError occurred, and the graph will stopped, but when I set a node to ainvoke subgraph, subgraph will not stop (continue to do ReAct loop) when asyncio.CancelledError occurred

System Info

System Information

OS: Linux
OS Version: #1 SMP PREEMPT_DYNAMIC Thu, 17 Jul 2025 21:05:29 +0000
Python Version: 3.12.11 (main, Jul 11 2025, 22:43:48) [Clang 20.1.4 ]

Package Information

langchain_core: 0.3.69
langchain: 0.3.26
langchain_community: 0.3.27
langsmith: 0.4.8
langchain_anthropic: 0.3.17
langchain_cohere: 0.4.4
langchain_deepseek: 0.1.3
langchain_experimental: 0.3.4
langchain_google_community: 2.0.7
langchain_google_genai: 2.1.8
langchain_mcp_adapters: 0.1.9
langchain_mistralai: 0.2.11
langchain_ollama: 0.3.5
langchain_openai: 0.3.28
langchain_tavily: 0.2.10
langchain_text_splitters: 0.3.8
langchain_xai: 0.2.4
langgraph_sdk: 0.1.74
langgraph_supervisor: 0.0.27
langgraph_swarm: 0.0.13

Optional packages not installed

langserve

Other Dependencies

aiohttp: 3.12.14
aiohttp<4,>=3.9.1: Installed. No version info available.
aiohttp<4.0.0,>=3.8.3: Installed. No version info available.
anthropic<1,>=0.57.0: Installed. No version info available.
async-timeout<5.0.0,>=4.0.0;: Installed. No version info available.
beautifulsoup4: 4.13.4
cohere: 5.16.1
dataclasses-json<0.7,>=0.5.7: Installed. No version info available.
db-dtypes: Installed. No version info available.
filetype: 1.2.0
gapic-google-longrunning: Installed. No version info available.
google-ai-generativelanguage: 0.6.18
google-api-core: 2.25.1
google-api-python-client: 2.176.0
google-auth: 2.40.3
google-auth-httplib2: 0.2.0
google-auth-oauthlib: Installed. No version info available.
google-cloud-aiplatform: Installed. No version info available.
google-cloud-bigquery: Installed. No version info available.
google-cloud-bigquery-storage: Installed. No version info available.
google-cloud-contentwarehouse: Installed. No version info available.
google-cloud-core: 2.4.3
google-cloud-discoveryengine: Installed. No version info available.
google-cloud-documentai: Installed. No version info available.
google-cloud-documentai-toolbox: Installed. No version info available.
google-cloud-speech: Installed. No version info available.
google-cloud-storage: Installed. No version info available.
google-cloud-texttospeech: Installed. No version info available.
google-cloud-translate: Installed. No version info available.
google-cloud-vision: Installed. No version info available.
googlemaps: Installed. No version info available.
grpcio: 1.73.1
httpx: 0.28.1
httpx-sse<1,>=0.3.1: Installed. No version info available.
httpx-sse<1.0.0,>=0.4.0: Installed. No version info available.
httpx<1,>=0.25.2: Installed. No version info available.
httpx>=0.25.2: Installed. No version info available.
jsonpatch<2.0,>=1.33: Installed. No version info available.
langchain-anthropic;: Installed. No version info available.
langchain-aws;: Installed. No version info available.
langchain-azure-ai;: Installed. No version info available.
langchain-cohere;: Installed. No version info available.
langchain-community;: Installed. No version info available.
langchain-core<0.4,>=0.3.36: Installed. No version info available.
langchain-core<0.4.0,>=0.3.40: Installed. No version info available.
langchain-core<1.0.0,>=0.3.47: Installed. No version info available.
langchain-core<1.0.0,>=0.3.51: Installed. No version info available.
langchain-core<1.0.0,>=0.3.62: Installed. No version info available.
langchain-core<1.0.0,>=0.3.66: Installed. No version info available.
langchain-core<1.0.0,>=0.3.68: Installed. No version info available.
langchain-core<1.0.0,>=0.3.69: Installed. No version info available.
langchain-core>=0.3.40: Installed. No version info available.
langchain-deepseek;: Installed. No version info available.
langchain-fireworks;: Installed. No version info available.
langchain-google-genai;: Installed. No version info available.
langchain-google-vertexai;: Installed. No version info available.
langchain-groq;: Installed. No version info available.
langchain-huggingface;: Installed. No version info available.
langchain-mistralai;: Installed. No version info available.
langchain-ollama;: Installed. No version info available.
langchain-openai;: Installed. No version info available.
langchain-openai<0.4,>=0.3.18: Installed. No version info available.
langchain-openai<1.0.0,>=0.3.9: Installed. No version info available.
langchain-perplexity;: Installed. No version info available.
langchain-text-splitters<1.0.0,>=0.3.8: Installed. No version info available.
langchain-together;: Installed. No version info available.
langchain-xai;: Installed. No version info available.
langchain<1.0.0,>=0.3.26: Installed. No version info available.
langgraph-prebuilt>=0.1.7: Installed. No version info available.
langgraph>=0.3.5: Installed. No version info available.
langgraph>=0.5: Installed. No version info available.
langsmith-pyo3: Installed. No version info available.
langsmith>=0.1.125: Installed. No version info available.
langsmith>=0.1.17: Installed. No version info available.
langsmith>=0.3.45: Installed. No version info available.
mcp>=1.9.2: Installed. No version info available.
numpy>=1.26.2;: Installed. No version info available.
numpy>=2.1.0;: Installed. No version info available.
ollama<1.0.0,>=0.5.1: Installed. No version info available.
openai-agents: Installed. No version info available.
openai<2.0.0,>=1.86.0: Installed. No version info available.
opentelemetry-api: Installed. No version info available.
opentelemetry-exporter-otlp-proto-http: Installed. No version info available.
opentelemetry-sdk: Installed. No version info available.
orjson: 3.11.0
orjson>=3.10.1: Installed. No version info available.
packaging: 25.0
packaging>=23.2: Installed. No version info available.
pandas: 2.3.1
pyarrow: Installed. No version info available.
pydantic: 2.11.7
pydantic-settings<3.0.0,>=2.4.0: Installed. No version info available.
pydantic<3,>=2: Installed. No version info available.
pydantic<3.0.0,>=2.7.4: Installed. No version info available.
pydantic>=2.7.4: Installed. No version info available.
pytest: Installed. No version info available.
PyYAML>=5.3: Installed. No version info available.
requests: 2.32.4
requests-toolbelt: 1.0.0
requests<3,>=2: Installed. No version info available.
rich: 14.0.0
SQLAlchemy<3,>=1.4: Installed. No version info available.
tenacity!=8.4.0,<10,>=8.1.0: Installed. No version info available.
tenacity!=8.4.0,<10.0.0,>=8.1.0: Installed. No version info available.
tiktoken<1,>=0.7: Installed. No version info available.
tokenizers<1,>=0.15.1: Installed. No version info available.
types-pyyaml: 6.0.12.20250516
typing-extensions>=4.14.0: Installed. No version info available.
typing-extensions>=4.7: Installed. No version info available.
zstandard: 0.23.0

Originally created by @mahonzhan on GitHub (Jul 28, 2025). Originally assigned to: @casparb on GitHub. ### Checked other resources - [x] This is a bug, not a usage question. For questions, please use the LangChain Forum (https://forum.langchain.com/). - [x] I added a clear and detailed title that summarizes the issue. - [x] I read what a minimal reproducible example is (https://stackoverflow.com/help/minimal-reproducible-example). - [x] I included a self-contained, minimal example that demonstrates the issue INCLUDING all the relevant imports. The code run AS IS to reproduce the issue. ### Example Code ```python async def call_subgraph(state, config): output = await subgraph.ainvoke( input=state, config=config, ) messages = output["messages"] messages = messages[-1:] return { **output, "messages": messages, } parent_graph.add_node(node="call_subgraph", action=call_subgraph) ``` ### Error Message and Stack Trace (if applicable) ```shell ``` ### Description I use fastapi and graph.astream to provide event stream, when I cancel request, asyncio.CancelledError occurred, and the graph will stopped, but when I set a node to ainvoke subgraph, subgraph will not stop (continue to do ReAct loop) when asyncio.CancelledError occurred ### System Info System Information ------------------ > OS: Linux > OS Version: #1 SMP PREEMPT_DYNAMIC Thu, 17 Jul 2025 21:05:29 +0000 > Python Version: 3.12.11 (main, Jul 11 2025, 22:43:48) [Clang 20.1.4 ] Package Information ------------------- > langchain_core: 0.3.69 > langchain: 0.3.26 > langchain_community: 0.3.27 > langsmith: 0.4.8 > langchain_anthropic: 0.3.17 > langchain_cohere: 0.4.4 > langchain_deepseek: 0.1.3 > langchain_experimental: 0.3.4 > langchain_google_community: 2.0.7 > langchain_google_genai: 2.1.8 > langchain_mcp_adapters: 0.1.9 > langchain_mistralai: 0.2.11 > langchain_ollama: 0.3.5 > langchain_openai: 0.3.28 > langchain_tavily: 0.2.10 > langchain_text_splitters: 0.3.8 > langchain_xai: 0.2.4 > langgraph_sdk: 0.1.74 > langgraph_supervisor: 0.0.27 > langgraph_swarm: 0.0.13 Optional packages not installed ------------------------------- > langserve Other Dependencies ------------------ > aiohttp: 3.12.14 > aiohttp<4,>=3.9.1: Installed. No version info available. > aiohttp<4.0.0,>=3.8.3: Installed. No version info available. > anthropic<1,>=0.57.0: Installed. No version info available. > async-timeout<5.0.0,>=4.0.0;: Installed. No version info available. > beautifulsoup4: 4.13.4 > cohere: 5.16.1 > dataclasses-json<0.7,>=0.5.7: Installed. No version info available. > db-dtypes: Installed. No version info available. > filetype: 1.2.0 > gapic-google-longrunning: Installed. No version info available. > google-ai-generativelanguage: 0.6.18 > google-api-core: 2.25.1 > google-api-python-client: 2.176.0 > google-auth: 2.40.3 > google-auth-httplib2: 0.2.0 > google-auth-oauthlib: Installed. No version info available. > google-cloud-aiplatform: Installed. No version info available. > google-cloud-bigquery: Installed. No version info available. > google-cloud-bigquery-storage: Installed. No version info available. > google-cloud-contentwarehouse: Installed. No version info available. > google-cloud-core: 2.4.3 > google-cloud-discoveryengine: Installed. No version info available. > google-cloud-documentai: Installed. No version info available. > google-cloud-documentai-toolbox: Installed. No version info available. > google-cloud-speech: Installed. No version info available. > google-cloud-storage: Installed. No version info available. > google-cloud-texttospeech: Installed. No version info available. > google-cloud-translate: Installed. No version info available. > google-cloud-vision: Installed. No version info available. > googlemaps: Installed. No version info available. > grpcio: 1.73.1 > httpx: 0.28.1 > httpx-sse<1,>=0.3.1: Installed. No version info available. > httpx-sse<1.0.0,>=0.4.0: Installed. No version info available. > httpx<1,>=0.25.2: Installed. No version info available. > httpx>=0.25.2: Installed. No version info available. > jsonpatch<2.0,>=1.33: Installed. No version info available. > langchain-anthropic;: Installed. No version info available. > langchain-aws;: Installed. No version info available. > langchain-azure-ai;: Installed. No version info available. > langchain-cohere;: Installed. No version info available. > langchain-community;: Installed. No version info available. > langchain-core<0.4,>=0.3.36: Installed. No version info available. > langchain-core<0.4.0,>=0.3.40: Installed. No version info available. > langchain-core<1.0.0,>=0.3.47: Installed. No version info available. > langchain-core<1.0.0,>=0.3.51: Installed. No version info available. > langchain-core<1.0.0,>=0.3.62: Installed. No version info available. > langchain-core<1.0.0,>=0.3.66: Installed. No version info available. > langchain-core<1.0.0,>=0.3.68: Installed. No version info available. > langchain-core<1.0.0,>=0.3.69: Installed. No version info available. > langchain-core>=0.3.40: Installed. No version info available. > langchain-deepseek;: Installed. No version info available. > langchain-fireworks;: Installed. No version info available. > langchain-google-genai;: Installed. No version info available. > langchain-google-vertexai;: Installed. No version info available. > langchain-groq;: Installed. No version info available. > langchain-huggingface;: Installed. No version info available. > langchain-mistralai;: Installed. No version info available. > langchain-ollama;: Installed. No version info available. > langchain-openai;: Installed. No version info available. > langchain-openai<0.4,>=0.3.18: Installed. No version info available. > langchain-openai<1.0.0,>=0.3.9: Installed. No version info available. > langchain-perplexity;: Installed. No version info available. > langchain-text-splitters<1.0.0,>=0.3.8: Installed. No version info available. > langchain-together;: Installed. No version info available. > langchain-xai;: Installed. No version info available. > langchain<1.0.0,>=0.3.26: Installed. No version info available. > langgraph-prebuilt>=0.1.7: Installed. No version info available. > langgraph>=0.3.5: Installed. No version info available. > langgraph>=0.5: Installed. No version info available. > langsmith-pyo3: Installed. No version info available. > langsmith>=0.1.125: Installed. No version info available. > langsmith>=0.1.17: Installed. No version info available. > langsmith>=0.3.45: Installed. No version info available. > mcp>=1.9.2: Installed. No version info available. > numpy>=1.26.2;: Installed. No version info available. > numpy>=2.1.0;: Installed. No version info available. > ollama<1.0.0,>=0.5.1: Installed. No version info available. > openai-agents: Installed. No version info available. > openai<2.0.0,>=1.86.0: Installed. No version info available. > opentelemetry-api: Installed. No version info available. > opentelemetry-exporter-otlp-proto-http: Installed. No version info available. > opentelemetry-sdk: Installed. No version info available. > orjson: 3.11.0 > orjson>=3.10.1: Installed. No version info available. > packaging: 25.0 > packaging>=23.2: Installed. No version info available. > pandas: 2.3.1 > pyarrow: Installed. No version info available. > pydantic: 2.11.7 > pydantic-settings<3.0.0,>=2.4.0: Installed. No version info available. > pydantic<3,>=2: Installed. No version info available. > pydantic<3.0.0,>=2.7.4: Installed. No version info available. > pydantic>=2.7.4: Installed. No version info available. > pytest: Installed. No version info available. > PyYAML>=5.3: Installed. No version info available. > requests: 2.32.4 > requests-toolbelt: 1.0.0 > requests<3,>=2: Installed. No version info available. > rich: 14.0.0 > SQLAlchemy<3,>=1.4: Installed. No version info available. > tenacity!=8.4.0,<10,>=8.1.0: Installed. No version info available. > tenacity!=8.4.0,<10.0.0,>=8.1.0: Installed. No version info available. > tiktoken<1,>=0.7: Installed. No version info available. > tokenizers<1,>=0.15.1: Installed. No version info available. > types-pyyaml: 6.0.12.20250516 > typing-extensions>=4.14.0: Installed. No version info available. > typing-extensions>=4.7: Installed. No version info available. > zstandard: 0.23.0
yindo added the bugpending labels 2026-02-20 17:42:07 -05:00
Author
Owner

@mahonzhan commented on GitHub (Aug 5, 2025):

simple ways to reproduce in fastapi with langgraph:

@router.get("/test")
async def test(request: Request):
    async def generate():
        try:
            async for chunk in supervisor.astream(
                    {
                        "messages": [
                            {
                                "role": "user",
                                "content": "find US and New York state GDP in 2024. what % of US GDP was New York state?",
                            }
                        ]
                    },
                    subgraphs=True,
                    print_mode=["tasks"],
            ):
                yield chunk
        except asyncio.CancelledError:
            log.warning("cancel")

    return EventSourceResponse(generate())

when the research subagent call llm, cancel the request with CTRL+C, research subagent keep going with new llm calls. not work as expected.

@mahonzhan commented on GitHub (Aug 5, 2025): simple ways to reproduce in fastapi with langgraph: ``` @router.get("/test") async def test(request: Request): async def generate(): try: async for chunk in supervisor.astream( { "messages": [ { "role": "user", "content": "find US and New York state GDP in 2024. what % of US GDP was New York state?", } ] }, subgraphs=True, print_mode=["tasks"], ): yield chunk except asyncio.CancelledError: log.warning("cancel") return EventSourceResponse(generate()) ``` when the research subagent call llm, cancel the request with CTRL+C, research subagent keep going with new llm calls. not work as expected.
Author
Owner

@casparb commented on GitHub (Sep 11, 2025):

Hey @mahonzhan. When you're documenting issues in langchain-ai, please include a minimal, fully self-contained example that directly demonstrates the issue that you're experiencing. I wrote a script but was unable to reproduce this behavior:

see code
import asyncio
from typing import TypedDict

import uuid
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver

class State(TypedDict):
    i: int

async def work_node(state: State) -> State:
    print("child: started", flush=True)
    try:
        j = 0
        while True:
            await asyncio.sleep(0.1)
            print(f"child: tick {j}", flush=True)
            j += 1
    except asyncio.CancelledError:
        print("child: got CancelledError", flush=True)
        raise

subgraph_builder = StateGraph(State)
subgraph_builder.add_node("work", work_node)
subgraph_builder.set_entry_point("work")
subgraph_builder.add_edge("work", END)
subgraph = subgraph_builder.compile()

parent = StateGraph(State)
parent.add_node("child", subgraph)
parent.set_entry_point("child")
parent.add_edge("child", END)

graph = parent.compile(checkpointer=InMemorySaver())

config = {""
    "configurable": {
        "thread_id": str(uuid.uuid4())
    }
}

async def main():
    task = asyncio.create_task(graph.ainvoke({"i": 0}, config))

    await asyncio.sleep(0.5)

    print("parent: cancelling run now", flush=True)
    task.cancel()
    try:
        await task
    except asyncio.CancelledError:
        print("parent: run cancelled", flush=True)

    await asyncio.sleep(1.0)
    print("parent: done", flush=True)

if __name__ == "__main__":
    asyncio.run(main())
$ python ./repro_cancel_subgraph.py
child: started
child: tick 0
child: tick 1
child: tick 2
child: tick 3
parent: cancelling run now
child: got CancelledError
parent: run cancelled
parent: done

Closing this issue. If you're still having problems please comment with code example and I'll reopen.

@casparb commented on GitHub (Sep 11, 2025): Hey @mahonzhan. When you're documenting issues in langchain-ai, please include a minimal, fully self-contained example that directly demonstrates the issue that you're experiencing. I wrote a script but was unable to reproduce this behavior: <details> <summary>see code</summary> ```python import asyncio from typing import TypedDict import uuid from langgraph.graph import StateGraph, START, END from langgraph.checkpoint.memory import InMemorySaver class State(TypedDict): i: int async def work_node(state: State) -> State: print("child: started", flush=True) try: j = 0 while True: await asyncio.sleep(0.1) print(f"child: tick {j}", flush=True) j += 1 except asyncio.CancelledError: print("child: got CancelledError", flush=True) raise subgraph_builder = StateGraph(State) subgraph_builder.add_node("work", work_node) subgraph_builder.set_entry_point("work") subgraph_builder.add_edge("work", END) subgraph = subgraph_builder.compile() parent = StateGraph(State) parent.add_node("child", subgraph) parent.set_entry_point("child") parent.add_edge("child", END) graph = parent.compile(checkpointer=InMemorySaver()) config = {"" "configurable": { "thread_id": str(uuid.uuid4()) } } async def main(): task = asyncio.create_task(graph.ainvoke({"i": 0}, config)) await asyncio.sleep(0.5) print("parent: cancelling run now", flush=True) task.cancel() try: await task except asyncio.CancelledError: print("parent: run cancelled", flush=True) await asyncio.sleep(1.0) print("parent: done", flush=True) if __name__ == "__main__": asyncio.run(main()) ``` ``` $ python ./repro_cancel_subgraph.py child: started child: tick 0 child: tick 1 child: tick 2 child: tick 3 parent: cancelling run now child: got CancelledError parent: run cancelled parent: done ``` </details> Closing this issue. If you're still having problems please comment with code example and I'll reopen.
Author
Owner

@mahonzhan commented on GitHub (Sep 22, 2025):

Hi, @casparb Thank you for your reply.

example code:

# main.py
import asyncio
from typing import TypedDict

import uuid

import uvicorn
from fastapi import FastAPI
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver
from sse_starlette import EventSourceResponse

class State(TypedDict):
    i: int

async def work_node(state: State) -> State:
    print("child: started", flush=True)
    try:
        j = 0
        while True:
            await asyncio.sleep(0.1)
            print(f"child: tick {j}", flush=True)
            j += 1
    except asyncio.CancelledError:
        print("child: got CancelledError", flush=True)
        raise

subgraph_builder = StateGraph(State)
subgraph_builder.add_node("work", work_node)
subgraph_builder.set_entry_point("work")
subgraph_builder.add_edge("work", END)
subgraph = subgraph_builder.compile()

parent = StateGraph(State)
parent.add_node("child", subgraph)
parent.set_entry_point("child")
parent.add_edge("child", END)

graph = parent.compile(checkpointer=InMemorySaver())

app = FastAPI()

@app.get("/test")
async def test():
    async def generate():
        try:
            async for chunk in graph.astream(
                    input={
                        "i": 0
                    },
                    config={
                        "configurable": {
                            "thread_id": str(uuid.uuid4())
                        }
                    },
                    subgraphs=True,
                    print_mode=["tasks"],
                    durability="exit",
            ):
                yield chunk
        except asyncio.CancelledError:
            print("cancel")

    return EventSourceResponse(generate())


if __name__ == "__main__":
    uvicorn.run(app=app, port=8000)
# pyproject.toml
[project]
name = "langgraph-bug-report"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
    "fastapi>=0.117.1",
    "langgraph>=0.6.7",
    "sse-starlette>=3.0.2",
    "uvicorn>=0.36.0",
]

test command:

timeout 2 curl localhost:8000/test

result:

INFO:     127.0.0.1:37788 - "GET /test HTTP/1.1" 200 OK
[tasks] {'id': 'a8878420-a4b7-9e17-4d3e-eec03f0daff8', 'name': 'child', 'input': {'i': 0}, 'triggers': ('branch:to:child',)}
child: started
[tasks] [graph=('child:a8878420-a4b7-9e17-4d3e-eec03f0daff8',)] {'id': 'cbb21716-3145-7d8f-ba08-99bdc865d478', 'name': 'work', 'input': {'i': 0}, 'triggers': ('branch:to:work',)}
child: tick 0
child: tick 1
child: tick 2
child: tick 3
child: tick 4
child: tick 5
child: tick 6
child: tick 7
child: tick 8
child: tick 9
child: tick 10
child: tick 11
child: tick 12
child: tick 13
child: tick 14
child: tick 15
child: tick 16
child: tick 17
child: tick 18
cancel
child: tick 19
child: tick 20
child: tick 21
child: tick 22
child: tick 23
child: tick 24
child: tick 25
child: tick 26
child: tick 27
child: tick 28
child: tick 29
child: tick 30
child: tick 31
child: tick 32
child: tick 33
child: tick 34
child: tick 35
child: tick 36
child: tick 37
child: tick 38
child: tick 39

As mentioned earlier, child cannot stop.

@mahonzhan commented on GitHub (Sep 22, 2025): Hi, @casparb Thank you for your reply. example code: ``` # main.py import asyncio from typing import TypedDict import uuid import uvicorn from fastapi import FastAPI from langgraph.graph import StateGraph, START, END from langgraph.checkpoint.memory import InMemorySaver from sse_starlette import EventSourceResponse class State(TypedDict): i: int async def work_node(state: State) -> State: print("child: started", flush=True) try: j = 0 while True: await asyncio.sleep(0.1) print(f"child: tick {j}", flush=True) j += 1 except asyncio.CancelledError: print("child: got CancelledError", flush=True) raise subgraph_builder = StateGraph(State) subgraph_builder.add_node("work", work_node) subgraph_builder.set_entry_point("work") subgraph_builder.add_edge("work", END) subgraph = subgraph_builder.compile() parent = StateGraph(State) parent.add_node("child", subgraph) parent.set_entry_point("child") parent.add_edge("child", END) graph = parent.compile(checkpointer=InMemorySaver()) app = FastAPI() @app.get("/test") async def test(): async def generate(): try: async for chunk in graph.astream( input={ "i": 0 }, config={ "configurable": { "thread_id": str(uuid.uuid4()) } }, subgraphs=True, print_mode=["tasks"], durability="exit", ): yield chunk except asyncio.CancelledError: print("cancel") return EventSourceResponse(generate()) if __name__ == "__main__": uvicorn.run(app=app, port=8000) ``` ``` # pyproject.toml [project] name = "langgraph-bug-report" version = "0.1.0" description = "Add your description here" readme = "README.md" requires-python = ">=3.12" dependencies = [ "fastapi>=0.117.1", "langgraph>=0.6.7", "sse-starlette>=3.0.2", "uvicorn>=0.36.0", ] ``` test command: ``` timeout 2 curl localhost:8000/test ``` result: ``` INFO: 127.0.0.1:37788 - "GET /test HTTP/1.1" 200 OK [tasks] {'id': 'a8878420-a4b7-9e17-4d3e-eec03f0daff8', 'name': 'child', 'input': {'i': 0}, 'triggers': ('branch:to:child',)} child: started [tasks] [graph=('child:a8878420-a4b7-9e17-4d3e-eec03f0daff8',)] {'id': 'cbb21716-3145-7d8f-ba08-99bdc865d478', 'name': 'work', 'input': {'i': 0}, 'triggers': ('branch:to:work',)} child: tick 0 child: tick 1 child: tick 2 child: tick 3 child: tick 4 child: tick 5 child: tick 6 child: tick 7 child: tick 8 child: tick 9 child: tick 10 child: tick 11 child: tick 12 child: tick 13 child: tick 14 child: tick 15 child: tick 16 child: tick 17 child: tick 18 cancel child: tick 19 child: tick 20 child: tick 21 child: tick 22 child: tick 23 child: tick 24 child: tick 25 child: tick 26 child: tick 27 child: tick 28 child: tick 29 child: tick 30 child: tick 31 child: tick 32 child: tick 33 child: tick 34 child: tick 35 child: tick 36 child: tick 37 child: tick 38 child: tick 39 ``` As mentioned earlier, child cannot stop.
Author
Owner

@chengjinluo commented on GitHub (Nov 18, 2025):

After some digging, I found that when the request disconnected, anyio (used by fastapi) will consistently cancel all new tasks awaited in the api handler coroutine, which also prevents the running of langgraph AsyncPregelLoop's finalizer aexit task

@chengjinluo commented on GitHub (Nov 18, 2025): After some digging, I found that when the request disconnected, anyio (used by fastapi) will [consistently cancel all new tasks](https://github.com/agronholm/anyio/blob/8967d8e28714e3c6e20a5c7c307d83e28e1892af/src/anyio/_backends/_asyncio.py#L578) awaited in the api handler coroutine, which also prevents the running of [langgraph AsyncPregelLoop's finalizer __aexit__ task](https://github.com/langchain-ai/langgraph/blob/df8becd5cfc74bb09ab2d7cbdf17c5559114b8d8/libs/langgraph/langgraph/pregel/_loop.py#L1311)
Author
Owner

@dayeguilaiye commented on GitHub (Dec 2, 2025):

@casparb Hi, I found this problem too. And the code which provided by @mahonzhan can reproduce the problem(I tried). And the explain from @chengjinluo seems possible.
Maybe this issue should be reopened? And any workaround or solution could be helpful!

@dayeguilaiye commented on GitHub (Dec 2, 2025): @casparb Hi, I found this problem too. And the code which provided by @mahonzhan can reproduce the problem(I tried). And the explain from @chengjinluo seems possible. Maybe this issue should be reopened? And any workaround or solution could be helpful!
Author
Owner

@casparb commented on GitHub (Dec 2, 2025):

Hi @dayeguilaiye @chengjinluo @mahonzhan
We will add this to our list of priorities. Thanks for the MRE

@casparb commented on GitHub (Dec 2, 2025): Hi @dayeguilaiye @chengjinluo @mahonzhan We will add this to our list of priorities. Thanks for the MRE
Author
Owner

@dayeguilaiye commented on GitHub (Dec 9, 2025):

I tried solving this with a monkey patch, and it worked.

Below is the monkey patch I'm currently using as a temporary workaround for this issue:

show patch code
_aexit_patched = False


def patch_langgraph_aexit_once() -> None:
    """Shield LangGraph AsyncPregelLoop.__aexit__ to ensure cleanup on cancel."""
    global _aexit_patched
    from langgraph.pregel import _loop

    if _aexit_patched:
        return

    orig_aexit = _loop.AsyncPregelLoop.__aexit__

    async def safe_aexit(
        self, exc_type, exc_value, traceback  # type: ignore[override]
    ):
        exit_task = asyncio.create_task(
            self.stack.__aexit__(exc_type, exc_value, traceback)
        )
        try:
            return await asyncio.shield(exit_task)
        except asyncio.CancelledError:
            with anyio.CancelScope(shield=True):
                await exit_task
            raise

    _loop.AsyncPregelLoop.__aexit__ = safe_aexit  # type: ignore[method-assign]
    _aexit_patched = True


# Apply the patch once on import.
patch_langgraph_aexit_once()

And here is the full code:

show full code
"""
Minimal workaround demo for LangGraph cancellation leak in FastAPI.

We monkey-patch AsyncPregelLoop.__aexit__ to shield the exit stack so
AsyncBackgroundExecutor.__aexit__ always runs and cancels child tasks,
even when the request CancelScope is already triggered.
"""

import asyncio
import uuid
from typing import TypedDict

import anyio
from fastapi import FastAPI
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import END, StateGraph
from sse_starlette import EventSourceResponse


_aexit_patched = False


def patch_langgraph_aexit_once() -> None:
    """Shield LangGraph AsyncPregelLoop.__aexit__ to ensure cleanup on cancel."""
    global _aexit_patched
    from langgraph.pregel import _loop

    if _aexit_patched:
        return

    orig_aexit = _loop.AsyncPregelLoop.__aexit__

    async def safe_aexit(
        self, exc_type, exc_value, traceback  # type: ignore[override]
    ):
        exit_task = asyncio.create_task(
            self.stack.__aexit__(exc_type, exc_value, traceback)
        )
        try:
            return await asyncio.shield(exit_task)
        except asyncio.CancelledError:
            with anyio.CancelScope(shield=True):
                await exit_task
            raise

    _loop.AsyncPregelLoop.__aexit__ = safe_aexit  # type: ignore[method-assign]
    _aexit_patched = True


# Apply the patch once on import.
patch_langgraph_aexit_once()


class State(TypedDict):
    i: int


async def work_node(state: State) -> State:
    print("child: started", flush=True)
    j = 0
    try:
        while True:
            await asyncio.sleep(0.1)
            print(f"child: tick {j}", flush=True)
            j += 1
    except asyncio.CancelledError:
        print("child: got CancelledError", flush=True)
        raise


subgraph_builder = StateGraph(State)
subgraph_builder.add_node("work", work_node)
subgraph_builder.set_entry_point("work")
subgraph_builder.add_edge("work", END)
subgraph = subgraph_builder.compile()

parent = StateGraph(State)
parent.add_node("child", subgraph)
parent.set_entry_point("child")
parent.add_edge("child", END)

graph = parent.compile(checkpointer=InMemorySaver())

app = FastAPI()


@app.get("/test")
async def test():
    async def generate():
        try:
            async for chunk in graph.astream(
                input={"i": 0},
                config={"configurable": {"thread_id": str(uuid.uuid4())}},
                subgraphs=True,
                print_mode=["tasks"],
                durability="exit",
            ):
                yield chunk
        except asyncio.CancelledError:
            print("cancel", flush=True)

    return EventSourceResponse(generate())


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app=app, port=8001)

It seems that a PR like the one below could fix the underlying issue, but I'm not very experienced with Python, so I'm not sure whether this is the best approach.

If this looks reasonable, I'd be happy to open a PR to address it.

PR content

Edit langgraph/pregel/_loop.py
from:

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_value: BaseException | None,
        traceback: TracebackType | None,
    ) -> bool | None:
        # unwind stack
        exit_task = asyncio.create_task(
            self.stack.__aexit__(exc_type, exc_value, traceback)
        )
        try:
            return await exit_task
        except asyncio.CancelledError as e:
            # Bubble up the exit task upon cancellation to permit the API
            # consumer to await it before e.g., reusing the DB connection.
            e.args = (*e.args, exit_task)
            raise

to:

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_value: BaseException | None,
        traceback: TracebackType | None,
    ) -> bool | None:
        # unwind stack
        exit_task = asyncio.create_task(
            self.stack.__aexit__(exc_type, exc_value, traceback)
        )
        try:
            return await asyncio.shield(exit_task)
        except asyncio.CancelledError as e:
            # Bubble up the exit task upon cancellation to permit the API
            # consumer to await it before e.g., reusing the DB connection.
            with anyio.CancelScope(shield=True):
                await exit_task
            e.args = (*e.args, exit_task)
            raise
@dayeguilaiye commented on GitHub (Dec 9, 2025): I tried solving this with a monkey patch, and it worked. Below is the monkey patch I'm currently using as a temporary workaround for this issue: <details> <summary>show patch code</summary> ```python _aexit_patched = False def patch_langgraph_aexit_once() -> None: """Shield LangGraph AsyncPregelLoop.__aexit__ to ensure cleanup on cancel.""" global _aexit_patched from langgraph.pregel import _loop if _aexit_patched: return orig_aexit = _loop.AsyncPregelLoop.__aexit__ async def safe_aexit( self, exc_type, exc_value, traceback # type: ignore[override] ): exit_task = asyncio.create_task( self.stack.__aexit__(exc_type, exc_value, traceback) ) try: return await asyncio.shield(exit_task) except asyncio.CancelledError: with anyio.CancelScope(shield=True): await exit_task raise _loop.AsyncPregelLoop.__aexit__ = safe_aexit # type: ignore[method-assign] _aexit_patched = True # Apply the patch once on import. patch_langgraph_aexit_once() ``` </details> And here is the full code: <details> <summary>show full code</summary> ```python """ Minimal workaround demo for LangGraph cancellation leak in FastAPI. We monkey-patch AsyncPregelLoop.__aexit__ to shield the exit stack so AsyncBackgroundExecutor.__aexit__ always runs and cancels child tasks, even when the request CancelScope is already triggered. """ import asyncio import uuid from typing import TypedDict import anyio from fastapi import FastAPI from langgraph.checkpoint.memory import InMemorySaver from langgraph.graph import END, StateGraph from sse_starlette import EventSourceResponse _aexit_patched = False def patch_langgraph_aexit_once() -> None: """Shield LangGraph AsyncPregelLoop.__aexit__ to ensure cleanup on cancel.""" global _aexit_patched from langgraph.pregel import _loop if _aexit_patched: return orig_aexit = _loop.AsyncPregelLoop.__aexit__ async def safe_aexit( self, exc_type, exc_value, traceback # type: ignore[override] ): exit_task = asyncio.create_task( self.stack.__aexit__(exc_type, exc_value, traceback) ) try: return await asyncio.shield(exit_task) except asyncio.CancelledError: with anyio.CancelScope(shield=True): await exit_task raise _loop.AsyncPregelLoop.__aexit__ = safe_aexit # type: ignore[method-assign] _aexit_patched = True # Apply the patch once on import. patch_langgraph_aexit_once() class State(TypedDict): i: int async def work_node(state: State) -> State: print("child: started", flush=True) j = 0 try: while True: await asyncio.sleep(0.1) print(f"child: tick {j}", flush=True) j += 1 except asyncio.CancelledError: print("child: got CancelledError", flush=True) raise subgraph_builder = StateGraph(State) subgraph_builder.add_node("work", work_node) subgraph_builder.set_entry_point("work") subgraph_builder.add_edge("work", END) subgraph = subgraph_builder.compile() parent = StateGraph(State) parent.add_node("child", subgraph) parent.set_entry_point("child") parent.add_edge("child", END) graph = parent.compile(checkpointer=InMemorySaver()) app = FastAPI() @app.get("/test") async def test(): async def generate(): try: async for chunk in graph.astream( input={"i": 0}, config={"configurable": {"thread_id": str(uuid.uuid4())}}, subgraphs=True, print_mode=["tasks"], durability="exit", ): yield chunk except asyncio.CancelledError: print("cancel", flush=True) return EventSourceResponse(generate()) if __name__ == "__main__": import uvicorn uvicorn.run(app=app, port=8001) ``` </details> It seems that a PR like the one below could fix the underlying issue, but I'm not very experienced with Python, so I'm not sure whether this is the best approach. If this looks reasonable, I'd be happy to open a PR to address it. <details> <summary>PR content</summary> Edit langgraph/pregel/_loop.py from: ```python async def __aexit__( self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, ) -> bool | None: # unwind stack exit_task = asyncio.create_task( self.stack.__aexit__(exc_type, exc_value, traceback) ) try: return await exit_task except asyncio.CancelledError as e: # Bubble up the exit task upon cancellation to permit the API # consumer to await it before e.g., reusing the DB connection. e.args = (*e.args, exit_task) raise ``` to: ```python async def __aexit__( self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, ) -> bool | None: # unwind stack exit_task = asyncio.create_task( self.stack.__aexit__(exc_type, exc_value, traceback) ) try: return await asyncio.shield(exit_task) except asyncio.CancelledError as e: # Bubble up the exit task upon cancellation to permit the API # consumer to await it before e.g., reusing the DB connection. with anyio.CancelScope(shield=True): await exit_task e.args = (*e.args, exit_task) raise ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraph#855