TypeError: 'NoneType' object is not callable #676

Closed
opened 2026-02-20 17:41:17 -05:00 by yindo · 6 comments
Owner

Originally created by @xlcaptain on GitHub (Jun 8, 2025).

TypeError: 'NoneType' object is not callable
File "/workspace/.venv/lib/python3.12/site-packages/langgraph/pregel/runner.py", line 108, in on_done
self.callback()(task, exception(fut))

This seems to happen when the callback is set to None or when self.callback() returns None, but the code does not check for this before calling it.

Additionally, I noticed that even after the main handler is cancelled (due to client disconnect), langgraph's internal retry logic (e.g., arun_with_retry) may still be running. When these retry or sub-tasks finish, their completion callback may already be set to None, resulting in the above TypeError in the logs.
Example log:

handle: <Handle functools.partial(<bound method FuturesDict.on_done of {<Task finished name='analysis' coro=<arun_with_retry() done, ...>
Exception in callback functools.partial(<bound method FuturesDict.on_done ...
TypeError: 'NoneType' object is not callable

To Reproduce

In production (FastAPI/Starlette streaming):

  1. Use langgraph in a FastAPI streaming endpoint (e.g., SSE).
  2. Start a long-running graph stream.
  3. Disconnect the client before the stream finishes (e.g., close the browser tab).
    In the backend, use code like:
    done, pending = await asyncio.wait([handler_task, cancellation_task], return_when=asyncio.FIRST_COMPLETED)
    for task in pending:
        task.cancel()
    
    to detect client disconnect and cancel the handler task.
  4. Observe the backend logs. You may see the above TypeError, especially if the graph contains nodes with retry logic (e.g., arun_with_retry).

In a simple local asyncio script (harder to reproduce):

import asyncio

async def main():
    async def consume_stream():
        try:
            async for event, event_data in graph.astream(
                    {"messages": [("user", "test message")], "mode": ['analysis', 'web_search']},
                    config={"configurable": {"thread_id": '123', "run_id": '1111', "user_id": '22'}},
                    stream_mode=["messages", "values"]
            ):
                print("event_data", event_data)
        except asyncio.CancelledError:
            print("Connection closed, task cancelled, cleaning up")
            raise

    task = asyncio.create_task(consume_stream())
    await asyncio.sleep(10)
    task.cancel()
    try:
        await task
    except asyncio.CancelledError:
        print("Main task caught cancellation")

asyncio.run(main())

Note:
This bug is more likely to occur in a real FastAPI/Starlette streaming environment, where the event loop and object lifetimes are more complex. In a simple local asyncio script, the callback may not be garbage collected, so the error does not always appear.

Expected behavior

No exception should be raised. The callback should be checked for None before being called, and the code should be robust to cancellation even when retry logic is involved.

Suggested Fix

In langgraph/pregel/runner.py, the on_done method should defensively check that both self.callback and the result of self.callback() are not None before calling:

def on_done(self, task, fut):
    try:
        cb = self.callback()
        if cb is not None:
            cb(task, _exception(fut))
    finally:
        # ... existing code ...

Environment

  • langgraph version: 0.4.8
  • Python version: 3.12
  • Any relevant dependencies: FastAPI, uvicorn, etc.

Additional context

This issue does not affect the main business logic, but it pollutes the logs and may confuse users.
If you need a minimal reproducible example, I can provide one.


Originally created by @xlcaptain on GitHub (Jun 8, 2025). **TypeError: 'NoneType' object is not callable** File "/workspace/.venv/lib/python3.12/site-packages/langgraph/pregel/runner.py", line 108, in on_done self.callback()(task, exception(fut)) This seems to happen when the callback is set to `None` or when `self.callback()` returns `None`, but the code does not check for this before calling it. Additionally, I noticed that even after the main handler is cancelled (due to client disconnect), langgraph's internal retry logic (e.g., `arun_with_retry`) may still be running. When these retry or sub-tasks finish, their completion callback may already be set to `None`, resulting in the above TypeError in the logs. Example log: ```python handle: <Handle functools.partial(<bound method FuturesDict.on_done of {<Task finished name='analysis' coro=<arun_with_retry() done, ...> Exception in callback functools.partial(<bound method FuturesDict.on_done ... TypeError: 'NoneType' object is not callable ``` ### To Reproduce #### In production (FastAPI/Starlette streaming): 1. Use langgraph in a FastAPI streaming endpoint (e.g., SSE). 2. Start a long-running graph stream. 3. Disconnect the client before the stream finishes (e.g., close the browser tab). In the backend, use code like: ```python done, pending = await asyncio.wait([handler_task, cancellation_task], return_when=asyncio.FIRST_COMPLETED) for task in pending: task.cancel() ``` to detect client disconnect and cancel the handler task. 4. Observe the backend logs. You may see the above TypeError, especially if the graph contains nodes with retry logic (e.g., `arun_with_retry`). #### In a simple local asyncio script (harder to reproduce): ```python import asyncio async def main(): async def consume_stream(): try: async for event, event_data in graph.astream( {"messages": [("user", "test message")], "mode": ['analysis', 'web_search']}, config={"configurable": {"thread_id": '123', "run_id": '1111', "user_id": '22'}}, stream_mode=["messages", "values"] ): print("event_data", event_data) except asyncio.CancelledError: print("Connection closed, task cancelled, cleaning up") raise task = asyncio.create_task(consume_stream()) await asyncio.sleep(10) task.cancel() try: await task except asyncio.CancelledError: print("Main task caught cancellation") asyncio.run(main()) ``` **Note:** This bug is more likely to occur in a real FastAPI/Starlette streaming environment, where the event loop and object lifetimes are more complex. In a simple local asyncio script, the callback may not be garbage collected, so the error does not always appear. ### Expected behavior No exception should be raised. The callback should be checked for `None` before being called, and the code should be robust to cancellation even when retry logic is involved. ### Suggested Fix In `langgraph/pregel/runner.py`, the `on_done` method should defensively check that both `self.callback` and the result of `self.callback()` are not `None` before calling: ```python def on_done(self, task, fut): try: cb = self.callback() if cb is not None: cb(task, _exception(fut)) finally: # ... existing code ... ``` ### Environment - langgraph version: 0.4.8 - Python version: 3.12 - Any relevant dependencies: FastAPI, uvicorn, etc. ### Additional context This issue does not affect the main business logic, but it pollutes the logs and may confuse users. If you need a minimal reproducible example, I can provide one. ---
yindo added the bugpending labels 2026-02-20 17:41:17 -05:00
yindo closed this issue 2026-02-20 17:41:17 -05:00
Author
Owner

@GrubHubbed commented on GitHub (Jun 11, 2025):

experiencing the same issue, anyone have relevant insights?

@GrubHubbed commented on GitHub (Jun 11, 2025): experiencing the same issue, anyone have relevant insights?
Author
Owner

@kongjiellx commented on GitHub (Jun 12, 2025):

+1

@kongjiellx commented on GitHub (Jun 12, 2025): +1
Author
Owner

@slmnsh commented on GitHub (Jun 16, 2025):

Any update on this @sydney-runkle?

@slmnsh commented on GitHub (Jun 16, 2025): Any update on this @sydney-runkle?
Author
Owner

@Morpheusai commented on GitHub (Jul 9, 2025):

            neoantigen_message = await NeoantigenSelection.ainvoke(
                {
                    "input_file": file_path,
                    "mhc_allele": mhc_allele,
                    "cdr3_sequence": cdr3 if cdr3 is not None else cdr3,
                    "tool_parameters": tool_parameters,
                    "patient_id": patient_id,
                    "predict_id": predict_id,
                    "conversation_id": conversation_id,
                }
            )
            
            if not isinstance(neoantigen_message, list) or len(neoantigen_message) < 9:
                send_ai_message_to_server(conversation_id, "\n⚠️ Something wrong in neo-antigen \n")
                return Command(goto=END)
                
            return Command(
                update={
                    "mhc_allele": mhc_allele,
                    "cdr3": cdr3,
                    "input_fsa_filepath": file_path,
                    "neoantigen_message": neoantigen_message
                },
                goto="patient_case_report"
            )

in our case, fastapi backend, ainvke takes a long time, and we updated langgraph into version-0.4.10, no error happens, but return Command seems not to be executed, hope someone can give us a help.

@Morpheusai commented on GitHub (Jul 9, 2025): ``` neoantigen_message = await NeoantigenSelection.ainvoke( { "input_file": file_path, "mhc_allele": mhc_allele, "cdr3_sequence": cdr3 if cdr3 is not None else cdr3, "tool_parameters": tool_parameters, "patient_id": patient_id, "predict_id": predict_id, "conversation_id": conversation_id, } ) if not isinstance(neoantigen_message, list) or len(neoantigen_message) < 9: send_ai_message_to_server(conversation_id, "\n⚠️ Something wrong in neo-antigen \n") return Command(goto=END) return Command( update={ "mhc_allele": mhc_allele, "cdr3": cdr3, "input_fsa_filepath": file_path, "neoantigen_message": neoantigen_message }, goto="patient_case_report" ) ``` in our case, fastapi backend, ainvke takes a long time, and we updated langgraph into version-0.4.10, no error happens, but return Command seems not to be executed, hope someone can give us a help.
Author
Owner

@yyyyyt123 commented on GitHub (Aug 6, 2025):

experiencing the same issue, which version of LangGraph should I use?

@yyyyyt123 commented on GitHub (Aug 6, 2025): experiencing the same issue, which version of LangGraph should I use?
Author
Owner

@sundaraa-deshaw commented on GitHub (Sep 5, 2025):

+1. We are hitting this bug in production. Which version of LangGraph solve this?

@sundaraa-deshaw commented on GitHub (Sep 5, 2025): +1. We are hitting this bug in production. Which version of LangGraph solve this?
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraph#676