fix(cli,session): remove nest_asyncio, make chat-profile config async-safe#2953
fix(cli,session): remove nest_asyncio, make chat-profile config async-safe#2953pidefrem wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
1 issue found across 3 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
|
This PR is stale because it has been open for 14 days with no activity. |
|
Not stale — this PR is active. I just pushed It's been waiting on a maintainer review. Could someone take a look when they have a chance? Happy to rebase or address feedback. Thanks! 🙏 |
dokterbob
left a comment
There was a problem hiding this comment.
Thanks! Some much welcomed cleanup/fixup.
|
This PR is stale because it has been open for 14 days with no activity. |
Head branch was pushed to by a user without write access
6aec8da to
dbda012
Compare
nest_asyncio.apply() was called at import time in chainlit/cli/__init__.py. nest_asyncio <= 1.6.0 internally calls asyncio.ensure_future(future, loop=self), where the loop= keyword was removed in Python 3.14 (bpo-39529). This corrupts asyncio task registration, causing asyncio.current_task() to return None inside running coroutines — which surfaced as a white page (HTTP 500 on every static asset) on every Chainlit app running on Python 3.14. asyncio.run(start()) is a top-level entry point and does not need re-entrant loop support, so nest_asyncio is removed entirely, along with the nest-asyncio dependency and its mypy override. A regression test guards against silent reintroduction. Refs Chainlit#2767
WebsocketSession.get_config() called asyncio.get_event_loop().run_until_complete() from within the already-running event loop (the SocketIO connect handler). This only worked because nest_asyncio patched run_until_complete to be re-entrant; removing nest_asyncio would silently break chat-profile config overrides on all Python versions. get_config() now returns the cached config immediately, and a new async resolve_config() properly awaits set_chat_profiles(). The connect() handler calls await session.resolve_config() after construction. Adds hatchling to the mypy optional deps (it ships py.typed; fixes a pre-existing import-not-found error in build.py). New tests cover overrides, idempotency, error handling, unknown profiles, and a regression test verifying run_until_complete is never called.
Kill the entire Chainlit process group (uv -> chainlit -> uvicorn) between specs and poll until the port is free, resolving an EADDRINUSE race in waitForPortFree. Exclude .pytest_cache from prettier checks. Also ensure the Cypress binary is present before tests run (a restored-but- incomplete cache, seen on windows-latest, leaves the npm package installed while the binary is missing) and set fail-fast: false so one shard's flake no longer cancels the rest of the matrix.
dbda012 to
42058b9
Compare
Summary
Problem 1 —
nest_asynciobreaks Python 3.14nest_asyncio.apply()is called at import time inchainlit/cli/__init__.py.nest_asyncio<= 1.6.0 internally callsasyncio.ensure_future(future, loop=self),where the
loop=keyword was removed in Python 3.14 (bpo-39529).This corrupts asyncio task registration, causing
asyncio.current_task()to returnNoneinside running coroutines.The symptom is a white page on every Chainlit app running on Python 3.14:
starlette's
FileResponsecallsanyio.to_thread.run_sync(), which callssniffio.current_async_library(), which callsasyncio.current_task()->None->
anyio.NoEventLoopError-> HTTP 500 on every static asset.Problem 2 —
get_config()relied onnest_asynciofor re-entrant event loopWebsocketSession.get_config()calledasyncio.get_event_loop().run_until_complete()from within the already-running event loop (the SocketIO
connecthandler).This only worked because
nest_asynciopatchedrun_until_completeto be re-entrant.Removing
nest_asynciowithout fixing this would silently break chat-profileconfig overrides on all Python versions (3.10-3.13), not just 3.14.
Fix
fix(cli): removenest_asyncioentirely.asyncio.run(start())is a top-level entry point; it does not need re-entrantloop support. Also removes the
nest-asynciodependency and its mypy override.Adds a regression test to prevent silent reintroduction.
fix(session): make chat-profile config resolution async-safe.get_config()now returns the cached config immediately (norun_until_complete)async resolve_config()method properlyawaitsset_chat_profiles()connect()handler callsawait session.resolve_config()after constructionand a regression test verifying
run_until_completeis never calledhatchlingto themypyoptional deps (it shipspy.typed;fixes a pre-existing
import-not-foundmypy error inbuild.py)test(e2e): harden Chainlit process cleanup and Cypress CI.uv->chainlit->uvicorn)between specs and polls until the port is free, resolving an EADDRINUSE race
.pytest_cachefrom Prettier checkscache on
windows-latestleft the npm package installed while the binary wasmissing) and sets
fail-fast: falseso one shard's flake no longer cancels therest of the matrix
Testing
All 798 tests pass on Python 3.10, 3.11, 3.12, and 3.13.
Python 3.14 cannot be tested yet (
pydantic-corelacks a 3.14 wheel).Note: the
requires-python < 3.14upper bound is intentionally left unchanged.Lifting it is a separate decision once the full dependency chain supports 3.14.
Refs #2767