benbjohnson/litestream · critical

init per-connection replica client: %w

Error message

init per-connection replica client: %w

What it means

In openMainDB, after creating a per-connection replica client from cfg.ReplicaURL, the client must be initialized with client.Init(context.Background()). This error is returned when Init fails AND the subsequent client.Close() also fails; both errors are joined with errors.Join. Litestream throws it because an uninitialized replica client cannot serve reads and the failed client also could not be cleaned up, signalling two simultaneous problems.

Source

Thrown at vfs.go:165

}

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
	f.CacheSize = vfs.CacheSize
	f.vfs = vfs
	f.perConnClient = perConnClient

	if cfg != nil {

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Read the joined error: fix the underlying Init failure first (credentials, endpoint, bucket existence, network reachability).
  2. Validate the replica_url independently before opening (e.g. construct and Init the client in a probe, or run 'litestream ltx -replica <url>').
  3. Check environment variables/credentials for the backend (AWS_*, GOOGLE_APPLICATION_CREDENTIALS, AZURE_STORAGE_*) are present and valid.
  4. If Close errors persist, upgrade the storage backend package — a failing Close on an already-failed client usually indicates a driver bug.
Defensive patterns

Strategy: try-catch

Validate before calling

client, err := litestream.NewReplicaClientFromURL(replicaURL)
if err == nil {
    if err := client.Init(ctx); err != nil { return fmt.Errorf("replica unreachable: %w", err) }
}

Try / catch

db, err := sql.Open("sqlite", dsn)
if err != nil {
    var joined interface{ Unwrap() []error }
    if errors.As(err, &multiErr) { /* inspect each joined error */ }
    return retryWithBackoff(func() error { return reopenDB(dsn) })
}

Prevention

When it happens

Trigger: Opening a VFS database whose per-connection replica_url points to a backend that fails Init (bad credentials, unreachable endpoint, missing bucket) and where the client implements io.Closer but Close() returns an error (e.g. session cleanup failure on sftp, HTTP client close error).

Common situations: Expired or wrong cloud credentials combined with flaky network; SFTP backend whose SSH session teardown fails; a custom ReplicaClient with buggy Close implementation.

Understand the failure class

Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.

Related errors


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