chainlit icon indicating copy to clipboard operation
chainlit copied to clipboard

Chainlit 2.8.5 Copilot mode throws Thread {thread_id} not found

Open jbcallaghan opened this issue 1 month ago • 10 comments

Describe the bug Since Chainlit 2.8.5, I now get Thread {thread_id} not found

To Reproduce .env CHAINLIT_AUTH_SECRET="Your secret" CUSTOM_AUTH=true

Frontend:

window.mountChainlitWidget({ chainlitServer: "http://127.0.0.1:8000", // Your Chainlit backend endpoint accessToken: accessToken, // Pass your generated JWT token theme: "light", customCssUrl: "http://127.0.0.1:8000/public/custom.css", expanded: false });

Full error:

File "c:\Program Files\Python311\Lib\site-packages\engineio\async_server.py", line 483, in run_async_handler return await self.handlersevent ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "c:\Program Files\Python311\Lib\site-packages\socketio\async_server.py", line 672, in _handle_eio_message await self._handle_connect(eio_sid, pkt.namespace, pkt.data) File "c:\Program Files\Python311\Lib\site-packages\socketio\async_server.py", line 551, in _handle_connect success = await self._trigger_event( ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "c:\Program Files\Python311\Lib\site-packages\socketio\async_server.py", line 637, in _trigger_event ret = await handler(*args) ^^^^^^^^^^^^^^^^^^^^ File "c:\Program Files\Python311\Lib\site-packages\chainlit\socket.py", line 142, in connect if not (await data_layer.get_thread_author(thread_id) == user.identifier): ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "c:\Program Files\Python311\Lib\site-packages\chainlit\data\chainlit_data_layer.py", line 420, in get_thread_author raise ValueError(f"Thread {thread_id} not found")

jbcallaghan avatar Nov 10 '25 13:11 jbcallaghan

The "Thread {thread_id} not found" error in Chainlit 2.8.5 Copilot mode is often caused by issues with thread management in custom data layers—especially if required methods like get_all_user_threads are missing or not correctly implemented. Inconsistencies in user/session identifiers can also break thread lookup and chat resumption. Make sure your data layer defines both userId and identifier fields consistently, and that threads are properly created and persisted before the frontend tries to connect or resume them. These problems have been reported in recent Chainlit versions, and a fix for related frontend-backend communication bugs is pending but not yet merged as of September 2025. You can find more details and example solutions in this discussion and this thread.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

dosubot[bot] avatar Nov 10 '25 13:11 dosubot[bot]

I am using the following:

@cl.data_layer def data_layer(): storage_client = S3StorageClient(bucket=s3_bucket,endpoint_url=s3_endpoint_url, aws_access_key_id=s3_access_key_id, aws_secret_access_key=s3_secret_access_key, verify=False) db_connection = f"postgresql://{db_username}:{db_password}@{db_endpoint}/{db_database}" return ChainlitDataLayer(database_url=db_connection,storage_client=storage_client)

jbcallaghan avatar Nov 10 '25 13:11 jbcallaghan

Your setup looks correct, but this error is often caused by issues in the custom data layer implementation. Make sure your ChainlitDataLayer correctly implements all required thread management methods, especially get_thread_author and get_all_user_threads, and that thread/user identifiers are stored and retrieved consistently.

If you're using PostgreSQL, always supply a non-null value (like {}) for the metadata field when creating or updating threads to avoid NOT NULL constraint errors—this can be done in hooks like @cl.on_chat_start or @cl.on_message. Example:

@cl.on_chat_start
async def on_chat_start():
    thread_id = cl.user_session.get("id")
    await cl_data._data_layer.update_thread(
        thread_id=thread_id,
        metadata={},  # Always provide a non-null value
    )

This workaround addresses a known bug in recent Chainlit versions where missing metadata can break thread lookup and cause this error. More details and examples are available here.

Also, double-check that threads are created and persisted before the frontend tries to connect or resume them, especially with custom authentication and Copilot mode.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

dosubot[bot] avatar Nov 10 '25 13:11 dosubot[bot]

ChainlitDataLayer is not my custom data layer, it's the official Chainlit one.

from chainlit.data.chainlit_data_layer import ChainlitDataLayer

I also tried the below, but the thread_id error occurs before cl.on_chat_start.

@cl.on_chat_start async def on_chat_start(): thread_id = cl.user_session.get("id") await cl_data._data_layer.update_thread( thread_id=thread_id, metadata={}, # Always provide a non-null value )

jbcallaghan avatar Nov 10 '25 14:11 jbcallaghan

The issue seems to be that on a new chat session, there isn't a thread in the database when get_thread_author is called. This is a new requirement introduced with PR #2637

jbcallaghan avatar Nov 10 '25 15:11 jbcallaghan

I can confirm this is only an issue when using co-pilot mode, so there seems to be a fundamental flaw in the design, where the changes made in 2..8.5 break co-pilot mode.

Using the same code but @cl.password_auth_callback and no co-pilot mode works without any other changes to the code.

jbcallaghan avatar Nov 11 '25 15:11 jbcallaghan

But then for some reason e2e tests succeed... I made PR anyway

asvishnyakov avatar Nov 12 '25 13:11 asvishnyakov

But then for some reason e2e tests succeed... I made PR anyway

Thank you.

It seems to stem from the way co-pilot uses custom auth and a JWT token, because with a normal implementation and @cl.password_auth_callback there isn't a problem

jbcallaghan avatar Nov 12 '25 13:11 jbcallaghan

@hayescode @sandangel FYI, looks like a blind spot in e2e tests

asvishnyakov avatar Nov 12 '25 13:11 asvishnyakov

@asvishnyakov would it be possible to add an additional check?

@sio.on("connect") # pyright: ignore [reportOptionalCall] async def connect(sid: str, environ, auth): user: User | PersistedUser | None = None token: str | None = None thread_id = auth.get("threadId") client_type = auth["clientType"]

if require_login():
    try:
        user, token = await _authenticate_connection(environ)

    except Exception as e:
        print("Exception authenticating connection: %s", e)
        logger.exception("Exception authenticating connection: %s", e)

    if not user:
        logger.error("Authentication failed in websocket connect.")
        raise ConnectionRefusedError("authentication failed")

    if thread_id and client_type != "copilot":
        data_layer = get_data_layer()
        if not data_layer:
            logger.error("Data layer is not initialized.")
            raise ConnectionRefusedError("data layer not initialized")

        if not (await data_layer.get_thread_author(thread_id) == user.identifier):
            logger.error("Authorization for the thread failed.")
            raise ConnectionRefusedError("authorization failed")

jbcallaghan avatar Nov 13 '25 10:11 jbcallaghan