Address PR review findings on the original shim:
1. Source-shape gating (avoid shadowing future upstream fixes)
The install now inspects the original implementation source and
only patches when it actually references `self._connection.stop` —
the buggy pattern this shim exists to fix. If a newer SQLAlchemy
ships a working `_terminate_force_close` we leave it alone.
2. Wake the aiosqlite worker, don't just flag it
In aiosqlite ≥0.20 the worker thread blocks on `_tx.get()`. Setting
`_running = False` alone never unblocks it — the queue needs a
sentinel push for the loop to observe the flag. The replacement
now puts `(None, None)` onto `_tx` first and then flips
`_running`, so the worker exits promptly instead of relying on a
subsequent unrelated queue item or GC.
3. Conditional install (no global mutation on non-sqlite deployments)
Move `_install_aiosqlite_compat()` inside the
`if 'sqlite' in ASYNC_SQLALCHEMY_DATABASE_URL` branch in
`internal/db.py`, so PostgreSQL / MySQL / etc. deployments get no
SQLAlchemy monkey-patch at all.
https://claude.ai/code/session_01JSr4NZSskEUQvoJnavVXh8
SQLAlchemy 2.0.36+ added a `terminate_force_close()` path on its
async DB-API adapters, used by the connection pool when a connection
must be invalidated immediately (typically when an in-flight query
was cancelled — request aborted by client, timeout, etc.).
For the aiosqlite dialect, `_terminate_force_close` unconditionally
accesses `self._connection.stop`. That attribute existed on
`aiosqlite.Connection` up to 0.18 (where Connection sub-classed
`threading.Thread`) and was removed in 0.20+ when the worker model
was refactored.
On the version pin Open WebUI ships (sqlalchemy 2.0.48 + aiosqlite
0.21.0) every cancelled aiosqlite call therefore produces a
multi-page
NotImplementedError: terminate_force_close() not implemented by this DBAPI shim
ERROR traceback, drowning real errors in noise even though the
underlying connection is correctly torn down by aiosqlite's own
worker on the next loop tick.
Add `internal/_aiosqlite_compat.install()` which monkey-patches the
shim to understand both APIs:
* aiosqlite ≤ 0.18 — call `Connection.stop()` as before.
* aiosqlite ≥ 0.20 — flip the worker thread's `_running` flag so it
exits on the next iteration (the same end-state the original code
achieved on the old API).
* Anything else — log at debug and fall back to GC.
The patch is idempotent, runs once at db.py import time before the
async engine is created, and is a no-op on installations whose
SQLAlchemy doesn't ship the affected symbol — so it won't break
future upstream rewrites.
https://claude.ai/code/session_01JSr4NZSskEUQvoJnavVXh8
* fix: drop extra='allow' on FolderForm and FolderUpdateForm
These request models were configured to accept arbitrary extra fields,
which were then merged into the folder row via form_data.model_dump().
In insert_new_folder the server-assigned user_id is placed before the
form spread, so a client-supplied user_id in the request body would
override it and the folder would be persisted against another account.
Strictly typed inputs are the correct shape for these endpoints — the
client has no legitimate reason to send fields beyond the declared
ones, and dropping extra='allow' closes the mass-assignment sink at
the validation layer instead of relying on every callsite to merge
fields in the right order.
* fix: reject unknown fields on FolderForm and FolderUpdateForm
Address review feedback: dropping extra='allow' fell back to Pydantic
v2's default extra='ignore', which only silently drops unknown fields
instead of rejecting them. The intent for these request models is a
strict input contract — fail fast when a client sends anything the
server does not expect — so explicitly set extra='forbid'. This also
makes the hardening visible in the form definition rather than implicit
in the default.