@@ -34,11 +34,8 @@ def _build_timeout() -> Timeout:
3434 )
3535
3636
37- def _asgi_client (timeout : Timeout ) -> AsyncClient :
38- """Create a local ASGI client."""
39- # Import on first local-client use so CLI help/version paths can import
40- # routing helpers without constructing the full FastAPI router graph.
41- from basic_memory .api .app import app as fastapi_app
37+ def _build_asgi_client (fastapi_app , timeout : Timeout ) -> AsyncClient :
38+ """Create a local ASGI client for an already-prepared FastAPI app."""
4239 from basic_memory .workspace_context import workspace_permalink_headers
4340
4441 return AsyncClient (
@@ -51,6 +48,33 @@ def _asgi_client(timeout: Timeout) -> AsyncClient:
5148 )
5249
5350
51+ async def _prepare_local_asgi_database (fastapi_app ) -> None :
52+ """Initialize local ASGI database state before the first request."""
53+ from basic_memory import db
54+
55+ config = ConfigManager ().config
56+ engine , session_maker = await db .get_or_create_db (config .database_path )
57+ fastapi_app .state .engine = engine
58+ fastapi_app .state .session_maker = session_maker
59+
60+
61+ @asynccontextmanager
62+ async def _asgi_client (timeout : Timeout ) -> AsyncIterator [AsyncClient ]:
63+ """Create a local ASGI client."""
64+ # Import on first local-client use so CLI help/version paths can import
65+ # routing helpers without constructing the full FastAPI router graph.
66+ from basic_memory .api .app import app as fastapi_app
67+
68+ # Trigger: local ASGITransport does not execute FastAPI lifespan startup.
69+ # Why: letting request dependencies initialize Postgres can run asyncpg DDL
70+ # under Starlette's request loop and trigger CPython's empty-ready-queue race.
71+ # Outcome: request handling sees the same app.state database objects as API
72+ # lifespan startup would have provided.
73+ await _prepare_local_asgi_database (fastapi_app )
74+ async with _build_asgi_client (fastapi_app , timeout ) as client :
75+ yield client
76+
77+
5478async def _resolve_cloud_token (config ) -> str :
5579 """Resolve cloud token with API key preferred, OAuth fallback."""
5680 with logfire .span (
@@ -260,7 +284,12 @@ def create_client() -> AsyncClient:
260284
261285 if _force_local_mode () or not _force_cloud_mode ():
262286 logger .info ("Creating ASGI client for local Basic Memory API" )
263- return _asgi_client (timeout )
287+ # Deprecated sync path: create_client() cannot await the local ASGI
288+ # pre-initialization used by get_client(), so callers that need proper
289+ # resource setup should use the async context manager instead.
290+ from basic_memory .api .app import app as fastapi_app
291+
292+ return _build_asgi_client (fastapi_app , timeout )
264293
265294 logger .info ("Creating HTTP client for cloud proxy (legacy create_client path)" )
266295 config = ConfigManager ().config
0 commit comments