Skip to content

[Bug] SQLite pool (default 5+10) not sized to the 200-thread limiter → /health throws QueuePool timeout under multi-session use #2767

Description

@jtafoya-db

Session Info

  • Session ID: conv_06103f86936e4271a4c3bc6f6b8b6f37
  • Agent: claude
  • Version: 0.5.1
  • OS: Darwin 25.5.0
  • Timestamp: 2026-07-17T03:27:11Z

What 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

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)

Metadata

Metadata

Assignees

Labels

BugSomething isn't workingP2-mediumPriority: bug with workaround, important feature requestcomp:dbDatabase, persistence, and migrationscomp:serverComponent: server, API, session managementtriagedIssue has been triaged by the botvalidated:reproducedRepro agent validated the issue can be reproduced

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions