On the default local SQLite backend, /health returns HTTP 500 with a SQLAlchemy pool timeout:
sqlalchemy.exc.TimeoutError: QueuePool limit of size 5 overflow 10 reached, connection timed out, timeout 30.00
Raised from db/utils.py (managed_session → PRAGMA foreign_keys = ON) via /health → _bulk_session_liveness → get_session_connectivity.
_create_engine (SQLite branch, omnigent/db/utils.py:231) sets only connect_args — no pool sizing — so SQLite inherits SQLAlchemy's default QueuePool(5, 10) = 15 connections. But server/app.py:_lifespan raises the AnyIO thread limiter to 200, and the Postgres branch is explicitly sized to match (pool_size=200). The SQLite branch was never aligned. The /health sidebar poll (every 1-2s, batched over every visible session id) exhausts the 15 slots under active-runner write-lock contention (readers block up to busy_timeout=20s), and the next checkout hits pool_timeout=30s. Regression from 0.4.x; present on main HEAD.
30 threads each holding a get_or_create_engine("sqlite:///…") connection for >30s reliably raises the timeout (eng.pool.status() shows Pool size: 5). Fix verified locally: pool_size=50, max_overflow=200 → Pool size: 50, no timeout. Opening a PR.
/health should not exhaust the connection pool under normal multi-session use. The SQLite pool should be sized to the 200-token thread limiter, the same way the Postgres branch already is.
ERROR: Exception in ASGI application
Traceback (most recent call last):
File "/Users/<user>/.local/share/uv/tools/omnigent/lib/python3.14/site-packages/uvicorn/protocols/http/httptools_impl.py", line 422, in run_asgi
result = await app( # type: ignore[func-returns-value]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
self.scope, self.receive, self.send
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
)
~
File "/Users/<user>/.local/share/uv/tools/omnigent/lib/python3.14/site-packages/uvicorn/middleware/proxy_headers.py", line 63, in __call__
return await self.app(scope, receive, send)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
File "/Users/<user>/.local/share/uv/tools/omnigent/lib/python3.14/site-packages/fastapi/applications.py", line 1163, in __call__
await super().__call__(scope, receive, send)
File "/Users/<user>/.local/share/uv/tools/omnigent/lib/python3.14/site-packages/starlette/applications.py", line 90, in __call__
await self.middleware_stack(scope, receive, send)
File "/Users/<user>/.local/share/uv/tools/omnigent/lib/python3.14/site-packages/starlette/middleware/errors.py", line 186, in __call__
raise exc
File "/Users/<user>/.local/share/uv/tools/omnigent/lib/python3.14/site-packages/starlette/middleware/errors.py", line 164, in __call__
await self.app(scope, receive, _send)
File "/Users/<user>/.local/share/uv/tools/omnigent/lib/python3.14/site-packages/starlette/middleware/base.py", line 193, in __call__
response = await self.dispatch_func(request, call_next)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
File "/Users/<user>/.local/share/uv/tools/omnigent/lib/python3.14/site-packages/omnigent/server/app.py", line 1510, in _record_server_metrics
response = await call_next(request)
~~~~~~~~~~~~~~~~~~~~~~~~
File "/Users/<user>/.local/share/uv/tools/omnigent/lib/python3.14/site-packages/starlette/middleware/base.py", line 168, in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
File "/Users/<user>/.local/share/uv/tools/omnigent/lib/python3.14/site-packages/starlette/middleware/base.py", line 144, in coro
await self.app(scope, receive_or_disconnect, send_no_error)
File "/Users/<user>/.local/share/uv/tools/omnigent/lib/pyth
──── (68 lines hidden) ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
s/<user>/.local/share/uv/tools/omnigent/lib/python3.14/site-packages/sqlalchemy/orm/session.py", line 2261, in _execute_internal
conn = self._connection_for_bind(bind)
File "/Users/<user>/.local/share/uv/tools/omnigent/lib/python3.14/site-packages/sqlalchemy/orm/session.py", line 2113, in _connection_for_bind
return trans._connection_for_bind(engine, execution_options)
~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<string>", line 2, in _connection_for_bind
File "/Users/<user>/.local/share/uv/tools/omnigent/lib/python3.14/site-packages/sqlalchemy/orm/state_changes.py", line 137, in _go
ret_value = fn(self, *arg, **kw)
File "/Users/<user>/.local/share/uv/tools/omnigent/lib/python3.14/site-packages/sqlalchemy/orm/session.py", line 1191, in _connection_for_bind
conn = bind.connect()
File "/Users/<user>/.local/share/uv/tools/omnigent/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 3295, in connect
return self._connection_cls(self)
~~~~~~~~~~~~~~~~~~~~^^^^^^
File "/Users/<user>/.local/share/uv/tools/omnigent/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 144, in __init__
self._dbapi_connection = engine.raw_connection()
~~~~~~~~~~~~~~~~~~~~~^^
File "/Users/<user>/.local/share/uv/tools/omnigent/lib/python3.14/site-packages/sqlalchemy/engine/base.py", line 3319, in raw_connection
return self.pool.connect()
~~~~~~~~~~~~~~~~~^^
File "/Users/<user>/.local/share/uv/tools/omnigent/lib/python3.14/site-packages/sqlalchemy/pool/base.py", line 448, in connect
return _ConnectionFairy._checkout(self)
~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
File "/Users/<user>/.local/share/uv/tools/omnigent/lib/python3.14/site-packages/sqlalchemy/pool/base.py", line 1272, in _checkout
fairy = _ConnectionRecord.checkout(pool)
File "/Users/<user>/.local/share/uv/tools/omnigent/lib/python3.14/site-packages/sqlalchemy/pool/base.py", line 712, in checkout
rec = pool._do_get()
File "/Users/<user>/.local/share/uv/tools/omnigent/lib/python3.14/site-packages/sqlalchemy/pool/impl.py", line 167, in _do_get
raise exc.TimeoutError(
...<4 lines>...
)
sqlalchemy.exc.TimeoutError: QueuePool limit of size 5 overflow 10 reached, connection timed out, timeout 30.00 (Background on this error at: https://sqlalche.me/e/20/3o7r)
Session Info
conv_06103f86936e4271a4c3bc6f6b8b6f37What happened
On the default local SQLite backend, /health returns HTTP 500 with a SQLAlchemy pool timeout:
sqlalchemy.exc.TimeoutError: QueuePool limit of size 5 overflow 10 reached, connection timed out, timeout 30.00
Raised from db/utils.py (managed_session → PRAGMA foreign_keys = ON) via /health → _bulk_session_liveness → get_session_connectivity.
Root cause:
_create_engine (SQLite branch, omnigent/db/utils.py:231) sets only connect_args — no pool sizing — so SQLite inherits SQLAlchemy's default QueuePool(5, 10) = 15 connections. But server/app.py:_lifespan raises the AnyIO thread limiter to 200, and the Postgres branch is explicitly sized to match (pool_size=200). The SQLite branch was never aligned. The /health sidebar poll (every 1-2s, batched over every visible session id) exhausts the 15 slots under active-runner write-lock contention (readers block up to busy_timeout=20s), and the next checkout hits pool_timeout=30s. Regression from 0.4.x; present on main HEAD.
Repro:
30 threads each holding a get_or_create_engine("sqlite:///…") connection for >30s reliably raises the timeout (eng.pool.status() shows Pool size: 5). Fix verified locally: pool_size=50, max_overflow=200 → Pool size: 50, no timeout. Opening a PR.
Expected behavior
/health should not exhaust the connection pool under normal multi-session use. The SQLite pool should be sized to the 200-token thread limiter, the same way the Postgres branch already is.
Screenshots
Console / terminal output