Problem
10 production sqlite3.connect(...) sites use a bare local variable with no with block, no try/finally, and no guaranteed .close() on exception — across api/workspaces.py (×4), api/composers.py (×3), utils/cli_chat_reader.py (×2), utils/cursor_md_exporter.py (×1). On a long-lived Flask process, leaks accumulate before the user notices.
Suggested fix
Wrap every site in contextlib.closing(sqlite3.connect(...)). The sqlite3.Connection's native context manager only commits/rollbacks; it does not close. closing() is what guarantees the file descriptor and read-lock are released on scope exit (including via exception):
from contextlib import closing
with closing(sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)) as conn:
rows = conn.execute("SELECT ...").fetchall()
Test-side sqlite3.connect calls are out of scope (per-test fixtures, short-lived).
Severity
Medium / 5pt — listed in Will's eval week-1 plan for cppa-cursor-browser.
Problem
10 production
sqlite3.connect(...)sites use a bare local variable with nowithblock, notry/finally, and no guaranteed.close()on exception — acrossapi/workspaces.py(×4),api/composers.py(×3),utils/cli_chat_reader.py(×2),utils/cursor_md_exporter.py(×1). On a long-lived Flask process, leaks accumulate before the user notices.Suggested fix
Wrap every site in
contextlib.closing(sqlite3.connect(...)). Thesqlite3.Connection's native context manager only commits/rollbacks; it does not close.closing()is what guarantees the file descriptor and read-lock are released on scope exit (including via exception):Test-side
sqlite3.connectcalls are out of scope (per-test fixtures, short-lived).Severity
Medium / 5pt — listed in Will's eval week-1 plan for
cppa-cursor-browser.