benbjohnson/litestream · critical

create per-connection replica client: %w

Error message

create per-connection replica client: %w

What it means

In openMainDB, when a VFS connection specifies cfg.ReplicaURL (via URI parameter 'replica_url' or per-connection config), a dedicated replica client is created from that URL with NewReplicaClientFromURL. This error wraps the failure of that URL-to-client construction. Litestream throws it because the read-only VFS cannot open the database without a working replica client backend.

Source

Thrown at vfs.go:160

	case vfs.requiresTempFile(flags):
		return vfs.openTempFile(name, flags)
	default:
		return nil, flags, sqlite3vfs.CantOpenError
	}
}

func (vfs *VFS) openMainDB(name string, uriParameters map[string]string, flags sqlite3vfs.OpenFlag) (sqlite3vfs.File, sqlite3vfs.OpenFlag, error) {
	cfg, err := vfs.configForOpen(name, uriParameters)
	if err != nil {
		return nil, 0, err
	}

	client := vfs.client
	var perConnClient bool
	if cfg != nil && cfg.ReplicaURL != "" {
		client, err = NewReplicaClientFromURL(cfg.ReplicaURL)
		if err != nil {
			return nil, 0, fmt.Errorf("create per-connection replica client: %w", err)
		}
		if err := client.Init(context.Background()); err != nil {
			if closer, ok := client.(io.Closer); ok {
				if closeErr := closer.Close(); closeErr != nil {
					return nil, 0, fmt.Errorf("init per-connection replica client: %w", errors.Join(err, closeErr))
				}
			}
			return nil, 0, fmt.Errorf("init per-connection replica client: %w", err)
		}
		perConnClient = true
	}

	if client == nil {
		return nil, 0, fmt.Errorf("no replica client configured: set LITESTREAM_REPLICA_URL, use SetVFSConfig, or pass replica_url in the database URI")
	}

	f := NewVFSFile(client, name, vfs.logger.With("name", name))
	f.PollInterval = vfs.PollInterval

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Check the replica_url scheme is one of the registered backends (s3, gs, azure, file, sftp) and spelled correctly.
  2. Ensure the storage backend package is imported (directly or via the litestream all-packages import) so its replica client is registered.
  3. URL-encode the replica_url properly inside the database URI; test the same URL with 'litestream replicate' to validate it.
  4. Fall back to the global configuration: set LITESTREAM_REPLICA_URL or call SetVFSConfig instead of the per-connection URL.

Example fix

// before
sql.Open("sqlite", "file:app.db?vfs=litestream&replica_url=s33://bucket/db")

// after
sql.Open("sqlite", "file:app.db?vfs=litestream&replica_url=s3://my-bucket/app.db")
Defensive patterns

Strategy: validation

Validate before calling

u, err := url.Parse(replicaURL)
if err != nil || u.Scheme == "" {
    return fmt.Errorf("invalid replica_url %q", replicaURL)
}
switch u.Scheme {
case "s3", "gs", "azure", "file", "sftp":
    // ok
default:
    return fmt.Errorf("unregistered replica scheme %q", u.Scheme)
}

Try / catch

db, err := sql.Open("sqlite", dsn)
if err != nil {
    var probeErr error
    _, probeErr = litestream.NewReplicaClientFromURL(replicaURL)
    return fmt.Errorf("open failed; replica client error: %w", errors.Join(err, probeErr))
}

Prevention

When it happens

Trigger: Opening a database with a URI like 'file:/path/db?vfs=litestream&replica_url=s3://bucket/db' where the replica_url scheme is unregistered/unknown (e.g. typo 's33://'), or the URL is malformed so the scheme/endpoint cannot be extracted.

Common situations: Typos in the replica URL scheme; using a backend whose driver package was not imported/registered (e.g. missing Azure/GCS client registration); copy-pasting URLs with extra characters or wrong quoting in connection strings.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06). Data as JSON: /api/errors/208618bf77253d0a. Report an issue: GitHub.