tursodatabase/turso · error · SqliteException

8

8

Error message

SQLite Error 8: 'attempt to write a readonly database'.

What it means

SqliteCommand's managed execution path throws a simulated SQLITE_READONLY (code 8, 'attempt to write a readonly database') when the connection was opened in read-only mode (Connection.IsReadOnly) and the statement list contains any write statement (INSERT/UPDATE/DELETE/DDL). The operation is rejected before touching the database.

Source

Thrown at bindings/dotnet/src/Turso.Data.Sqlite/SqliteCommand.cs:370

            }
        }
    }

    private async Task<SqliteDataReader> ExecuteManagedAsync(
        string method,
        CommandBehavior behavior,
        CancellationToken cancellationToken)
    {
        EnsureExecutable(method);
        var statements = GetManagedStatements();
        if (Connection!.HasOpenReader && statements.Any(IsWriteStatement))
        {
            await Task.Delay(TimeSpan.FromSeconds(CommandTimeout), cancellationToken).ConfigureAwait(false);
            throw new SqliteException(Properties.Resources.SqliteNativeError(5, "database is locked"), 5);
        }
        if (Connection!.IsReadOnly && statements.Any(IsWriteStatement))
        {
            throw new SqliteException(
                Properties.Resources.SqliteNativeError(8, "attempt to write a readonly database"),
                8);
        }
        if (statements.Count == 0)
        {
            _hasOpenReader = true;
            Connection!.ReaderOpened();
            return new SqliteDataReader(this, -1, behavior, CloseReader);
        }

        ValidateManagedParameterValues();
        var results = new List<ManagedSqliteResult>();
        var recordsAffected = 0;
        var hadResultSet = false;
        foreach (var statement in statements)
        {
            cancellationToken.ThrowIfCancellationRequested();
            if (TryHandleFacadeStatement(statement.Sql, out var sql))

View on GitHub (pinned to 6c72522679)

Solutions

  1. Reopen the connection without Mode=ReadOnly (e.g. Mode=ReadWrite or default) for write workloads
  2. Use two connections: a read-only one for queries and a writable one for mutations
  3. Check Connection.IsReadOnly (or the connection string) before issuing writes and route accordingly
  4. Fix file permissions if the read-only mode was unintentional (e.g. read-only mount)

Example fix

// before
var conn = new SqliteConnection("Data Source=app.db;Mode=ReadOnly");
conn.Execute("INSERT INTO t VALUES (1)"); // throws code 8
// after
var conn = new SqliteConnection("Data Source=app.db;Mode=ReadWrite");
conn.Execute("INSERT INTO t VALUES (1)");
Defensive patterns

Strategy: validation

Validate before calling

if (connection.IsReadOnly && IsWriteSql(commandText))
    throw new InvalidOperationException("Cannot execute write SQL on a read-only connection.");

Try / catch

try { cmd.ExecuteNonQuery(); }
catch (SqliteException ex) when (ex.SqliteErrorCode == 8)
{
    // reroute to writable connection
    writableConnection.Execute(cmd.CommandText);
}

Prevention

When it happens

Trigger: Opening the connection with Mode=ReadOnly in the connection string (or a read-only datasource) and executing INSERT/UPDATE/DELETE/CREATE/etc. via ExecuteNonQuery/ExecuteReader on a managed connection.

Common situations: Connection strings copied from a read-only analytics setup being reused for write endpoints; environment-specific config (prod writes allowed, CI/dataset mounted read-only); accidentally passing a read-only file path or read-only replica options.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of tursodatabase/turso@6c72522679 (2026-09-06). Data as JSON: /api/errors/274eaad7cf7192fc. Report an issue: GitHub.