fix(kanban): close half-open tracked connection when busy_timeout PRAGMA fails

_sqlite_connect opened a connection via connect_tracked and then ran the
busy_timeout PRAGMA; if that raised, the half-open connection was abandoned
— leaking its fd AND leaving a stale entry in the sqlite_safe_read
live-connection registry (which only clears on close), permanently blocking
byte-level probes of the kanban database. Close before re-raising.

Salvaged from PR #96290 (kanban slice) with regression test.
This commit is contained in:
Heath Harris
2026-08-27 18:45:33 +05:30
committed by kshitij
parent 5fc308a707
commit a71be9852e
3 changed files with 50 additions and 4 deletions
+33
View File
@@ -1007,6 +1007,39 @@ def test_connect_works_when_wal_is_silently_refused(tmp_path, monkeypatch, caplo
)
def test_sqlite_connect_closes_tracked_conn_on_setup_failure(tmp_path, monkeypatch):
"""A PRAGMA failure after connect must not abandon a tracked kanban fd."""
from hermes_cli import sqlite_safe_read
db_path = tmp_path / "kanban.db"
real_connect = sqlite3.connect
opened = []
class _BusyTimeoutFailure(sqlite3.Connection):
def execute(self, sql, *args, **kwargs): # type: ignore[override]
if str(sql).startswith("PRAGMA busy_timeout="):
raise sqlite3.OperationalError("simulated setup failure")
return super().execute(sql, *args, **kwargs)
def failing_connect(*args, **kwargs):
kwargs.pop("factory", None)
conn = real_connect(*args, factory=_BusyTimeoutFailure, **kwargs)
opened.append(conn)
return conn
key = sqlite_safe_read._key(db_path)
with sqlite_safe_read._live_lock:
before = sqlite_safe_read._live_connections.get(key, 0)
monkeypatch.setattr(kb.sqlite3, "connect", failing_connect)
with pytest.raises(sqlite3.OperationalError, match="simulated setup failure"):
kb._sqlite_connect(db_path)
with sqlite_safe_read._live_lock:
after = sqlite_safe_read._live_connections.get(key, 0)
assert after == before
def test_unlink_tasks_triggers_recompute_ready(kanban_home):
"""Regression test for issue #22459.