Skip to content
Latchkey

SQLite "database is locked" in CI

SQLite allows only one writer at a time. When two processes/threads try to write at once, the loser gets "database is locked" (SQLITE_BUSY). In CI this is usually parallel test workers contending for the same file.

What this error means

Tests or migrations fail intermittently with "database is locked", especially when run in parallel. The failure tracks concurrency, not database readiness.

psql
sqlite3.OperationalError: database is locked
# or
Error: SQLITE_BUSY: database is locked

Common causes

Concurrent writers on one file

Parallel test workers (or a writer plus a long read) contend for SQLite's single write lock, and the loser fails immediately by default.

No busy timeout configured

Without a busy timeout, SQLite returns SQLITE_BUSY instantly instead of waiting briefly for the lock to free.

Default rollback journal mode

The default journal mode serializes readers and writers more aggressively than WAL, increasing contention under parallelism.

How to fix it

Enable WAL and a busy timeout

WAL lets readers and a writer coexist; a busy timeout makes writers wait for the lock instead of failing instantly.

SQL
PRAGMA journal_mode=WAL;
PRAGMA busy_timeout=5000;

Reduce write concurrency

  1. Give each parallel worker its own database file when tests allow.
  2. Or serialize write-heavy tests so they do not contend for one file.
  3. Keep transactions short so the write lock is held briefly.

How to prevent it

  • Use WAL mode and a busy timeout for any concurrent SQLite access.
  • Isolate per-worker database files in parallel test runs.
  • On managed runners (Latchkey), self-healing auto-retries transient lock-contention failures, which buys time for a brief SQLITE_BUSY to clear.

Frequently asked questions

What causes ""database is locked""?
Parallel test workers (or a writer plus a long read) contend for SQLite's single write lock, and the loser fails immediately by default.
How do I fix "database is locked"?
WAL lets readers and a writer coexist; a busy timeout makes writers wait for the lock instead of failing instantly.
Can Latchkey fix this automatically?
Yes. Latchkey runs your GitHub Actions on managed runners that detect this failure, apply the fix, and retry the job automatically - self-healing is on by default.

Related guides

References

Latchkey auto-heals failures like this one - detected, fixed, and retried without you. Start free → 30-day trial · No credit card