SQLITE_READONLY
SQLiteERRORCommonAccess ControlHIGH confidence

attempt to write a readonly database

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
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 write — triggers SQLITE_READONLY
conn = sqlite3.connect('/tmp/demo.db')
conn.execute('INSERT INTO t VALUES (1)')

expected output

sqlite3.OperationalError: attempt to write a readonly database

Fix 1

Restore write permissions

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

Restore write permissions
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 When the source database must remain read-only (e.g., shipped with an app).

Open a copy of the database for writing
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

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.

Content generated with AI assistance and reviewed for accuracy. Found an error? hello@errcodes.dev

← All SQLite errors