pgref.dev/sqlite/errors/SQLITE_READONLY
SQLITE_READONLYERRORTier 1 — Safe✅ HIGH confidence

attempt to write a readonly database

Category: Access ControlVersions: All SQLite versions

What this means

SQLITE_READONLY (result code 8) is returned when SQLite attempts a write operation on a database that was opened in read-only mode, or whose underlying file has OS-level read-only permissions. The check is performed before any disk I/O so the database is never partially modified.

Why it happens

  1. 1The database file or its containing directory has read-only OS permissions (chmod 444)
  2. 2The connection was opened with the SQLITE_OPEN_READONLY flag
  3. 3The database is on a read-only filesystem (e.g., CD-ROM, read-only NFS mount)
  4. 4The WAL file or shared-memory file exists but is not writable while the database itself is readable

How to reproduce

A database file is made read-only at the OS level and then a write is attempted.

trigger — this will ERROR
import sqlite3, os, stat

# Create and populate the database
conn = sqlite3.connect('/tmp/demo.db')
conn.execute('CREATE TABLE t (x INTEGER)')
conn.commit()
conn.close()

# Make it read-only
os.chmod('/tmp/demo.db', stat.S_IRUSR | stat.S_IRGRP)

# Attempt to writetriggers SQLITE_READONLY
conn = sqlite3.connect('/tmp/demo.db')
conn.execute('INSERT INTO t VALUES (1)')
sqlite3.OperationalError: attempt to write a readonly database

Fix 1: Restore write permissions

When the file should be writable but permissions were accidentally changed.

fix
import os, stat
os.chmod('/tmp/demo.db', stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP)

Why this works

SQLite checks the OS writability of the database file and its parent directory before opening a write transaction. Restoring write permission (mode 0644 or stricter) allows the library to acquire the write lock.

Fix 2: Open a copy of the database for writing

When the source database must remain read-only (e.g., shipped with an app).

fix
import sqlite3, shutil

shutil.copy('/assets/readonly.db', '/data/user.db')
conn = sqlite3.connect('/data/user.db')
conn.execute('INSERT INTO t VALUES (1)')
conn.commit()

Why this works

Copying to a writable location gives the application a mutable working copy while keeping the original read-only asset intact. Common pattern for mobile apps shipping a pre-populated SQLite database.

What not to do

Open the database with sqlite3.connect() and ignore the SQLITE_READONLY error

Why it's wrong: Silent swallowing of write errors means data changes are never persisted, leading to data loss and confusing application state.

Version notes

SQLite 3.8.0+SQLITE_READONLY_ROLLBACK (264) extended code added — emitted when a hot journal exists but the database is read-only, preventing rollback of an interrupted transaction.

Dangerous variant

⚠️ Warning

SQLITE_READONLY_ROLLBACK (264) — a hot journal from a previously interrupted write exists but cannot be applied because the database is now read-only. The database may be in an inconsistent state until the journal is replayed.

Sources

📚 Official docs: https://www.sqlite.org/rescode.html#readonly

🔧 Source ref: sqlite3.h — SQLITE_READONLY = 8

📖 Further reading: SQLite URI filenames and open flags

Confidence assessment

✅ HIGH confidence

Stable and well-documented. Extended codes for READONLY variants are listed in sqlite3.h and the official result code page.

See also

⚙️ This error reference was generated with AI assistance and reviewed for accuracy. Examples are provided to illustrate common scenarios and may not cover every case. Always test fixes in a development environment before applying to production. Spotted an error? Suggest a correction →