hermes - 💡(How to fix) Fix sqlite3.OperationalError: database is locked at first kanban dispatcher tick after every gateway startup

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Error Message

On every cold start, the gateway logs a sqlite3.OperationalError: database is locked error at the first kanban dispatcher tick (gateway/run.py, _kanban_dispatcher_watcher). The race is between _kanban_notifier_watcher and _kanban_dispatcher_watcher — both use an identical asyncio.sleep(5) startup delay, so their first ticks fire simultaneously. Each watcher calls _kb.connect(board=slug) (which auto-initialises the schema on first use via executescript + _migrate_add_optional_columns) and then immediately calls _kb.init_db(board=slug), which intentionally cache-busts _INITIALIZED_PATHS and re-runs the migration on a new connection. With two threads running this double-init pattern concurrently on the same DB file, the BEGIN IMMEDIATE in _migrate_add_optional_columns (~line 1032 of kanban_db.py) is issued by up to four connections simultaneously, and SQLite's write lock causes the error. The timeout=30 on sqlite3.connect() does not protect against this because it only applies to the connection call, not subsequently-issued SQL statements. The dispatcher self-recovers on the next tick, so the impact is cosmetic. Two potential fixes: (1) add a threading.Lock around the _INITIALIZED_PATHS check-and-set in connect() to serialise first-time init; (2) remove the redundant init_db() calls in both watchers, since connect() already handles schema init on first use.

Root Cause

On every cold start, the gateway logs a sqlite3.OperationalError: database is locked error at the first kanban dispatcher tick (gateway/run.py, _kanban_dispatcher_watcher). The race is between _kanban_notifier_watcher and _kanban_dispatcher_watcher — both use an identical asyncio.sleep(5) startup delay, so their first ticks fire simultaneously. Each watcher calls _kb.connect(board=slug) (which auto-initialises the schema on first use via executescript + _migrate_add_optional_columns) and then immediately calls _kb.init_db(board=slug), which intentionally cache-busts _INITIALIZED_PATHS and re-runs the migration on a new connection. With two threads running this double-init pattern concurrently on the same DB file, the BEGIN IMMEDIATE in _migrate_add_optional_columns (~line 1032 of kanban_db.py) is issued by up to four connections simultaneously, and SQLite's write lock causes the error. The timeout=30 on sqlite3.connect() does not protect against this because it only applies to the connection call, not subsequently-issued SQL statements. The dispatcher self-recovers on the next tick, so the impact is cosmetic. Two potential fixes: (1) add a threading.Lock around the _INITIALIZED_PATHS check-and-set in connect() to serialise first-time init; (2) remove the redundant init_db() calls in both watchers, since connect() already handles schema init on first use.

Fix Action

Fix / Workaround

On every cold start, the gateway logs a sqlite3.OperationalError: database is locked error at the first kanban dispatcher tick (gateway/run.py, _kanban_dispatcher_watcher). The race is between _kanban_notifier_watcher and _kanban_dispatcher_watcher — both use an identical asyncio.sleep(5) startup delay, so their first ticks fire simultaneously. Each watcher calls _kb.connect(board=slug) (which auto-initialises the schema on first use via executescript + _migrate_add_optional_columns) and then immediately calls _kb.init_db(board=slug), which intentionally cache-busts _INITIALIZED_PATHS and re-runs the migration on a new connection. With two threads running this double-init pattern concurrently on the same DB file, the BEGIN IMMEDIATE in _migrate_add_optional_columns (~line 1032 of kanban_db.py) is issued by up to four connections simultaneously, and SQLite's write lock causes the error. The timeout=30 on sqlite3.connect() does not protect against this because it only applies to the connection call, not subsequently-issued SQL statements. The dispatcher self-recovers on the next tick, so the impact is cosmetic. Two potential fixes: (1) add a threading.Lock around the _INITIALIZED_PATHS check-and-set in connect() to serialise first-time init; (2) remove the redundant init_db() calls in both watchers, since connect() already handles schema init on first use.

RAW_BUFFERClick to expand / collapse

On every cold start, the gateway logs a sqlite3.OperationalError: database is locked error at the first kanban dispatcher tick (gateway/run.py, _kanban_dispatcher_watcher). The race is between _kanban_notifier_watcher and _kanban_dispatcher_watcher — both use an identical asyncio.sleep(5) startup delay, so their first ticks fire simultaneously. Each watcher calls _kb.connect(board=slug) (which auto-initialises the schema on first use via executescript + _migrate_add_optional_columns) and then immediately calls _kb.init_db(board=slug), which intentionally cache-busts _INITIALIZED_PATHS and re-runs the migration on a new connection. With two threads running this double-init pattern concurrently on the same DB file, the BEGIN IMMEDIATE in _migrate_add_optional_columns (~line 1032 of kanban_db.py) is issued by up to four connections simultaneously, and SQLite's write lock causes the error. The timeout=30 on sqlite3.connect() does not protect against this because it only applies to the connection call, not subsequently-issued SQL statements. The dispatcher self-recovers on the next tick, so the impact is cosmetic. Two potential fixes: (1) add a threading.Lock around the _INITIALIZED_PATHS check-and-set in connect() to serialise first-time init; (2) remove the redundant init_db() calls in both watchers, since connect() already handles schema init on first use.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING