tursodatabase/turso · error · InvalidOperationException

A SqliteConnection wrapping a TursoConnection must remain a

Error message

A SqliteConnection wrapping a TursoConnection must remain a direct remote or embedded replica connection.

What it means

This InvalidOperationException is thrown by the SqliteConnection.ConnectionString setter when a connection that already wraps a TursoConnection is assigned a local connection string. The wrapper must stay on the managed direct-remote/embedded-replica backend for its lifetime.

Source

Thrown at bindings/dotnet/src/Turso.Data.Sqlite/SqliteConnection.cs:74

        _managedConnection = connection;
        _ownsManagedConnection = ownsConnection;
        _wrapsManagedConnection = true;
        _readOnly = options.Mode == SqliteOpenMode.ReadOnly;
    }

    [AllowNull]
    public override string ConnectionString
    {
        get => _connectionOptions.ConnectionString;
        set
        {
            if (State == ConnectionState.Open)
                throw new InvalidOperationException(Properties.Resources.ConnectionStringRequiresClosedConnection);

            var options = new SqliteConnectionStringBuilder(value);
            if (_wrapsManagedConnection && options.IsLocal)
            {
                throw new InvalidOperationException(
                    "A SqliteConnection wrapping a TursoConnection must remain a direct remote or embedded replica connection.");
            }

            ConfigureManagedConnection(options);
            _connectionOptions = options;
            _defaultTimeout = null;
        }
    }

    public override string Database => _managedConnection?.Database ?? "main";

    public override string DataSource => _managedConnection?.DataSource ?? _dataSource ?? _connectionOptions.DataSource;

    public int DefaultTimeout
    {
        get => _defaultTimeout ?? _connectionOptions.DefaultTimeout;
        set
        {

View on GitHub (pinned to 6c72522679)

Solutions

  1. Assign only direct remote or embedded replica connection strings to a wrapping SqliteConnection
  2. Create a new, separate SqliteConnection(connectionString) for local usage instead of mutating the wrapper
  3. Track the _wrapsManagedConnection flag in your own code and branch before reassigning the connection string

Example fix

// before
managedWrapper.ConnectionString = "Data Source=local.db"; // throws

// after
var localConn = new SqliteConnection("Data Source=local.db");
Defensive patterns

Strategy: validation

Validate before calling

if (new SqliteConnectionStringBuilder(cs).IsLocal && wrapsManaged)
    throw new InvalidOperationException("Use a fresh connection for local targets.");

Try / catch

try { conn.ConnectionString = cs; }
catch (InvalidOperationException ex) when (ex.Message.Contains("must remain"))
{
    conn = new SqliteConnection(cs);
}

Prevention

When it happens

Trigger: Setting conn.ConnectionString = "Data Source=local.db" (or any IsLocal string) on a SqliteConnection constructed around a TursoConnection, whether the connection is open or closed (the open-state guard fires first if open).

Common situations: Code that reuses one SqliteConnection object and swaps connection strings between local and remote targets; config-driven connection strings pointing to a local file for a wrapper meant to be remote.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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