{"record":{"id":"64f249562151942e","repo":"benbjohnson/litestream","slug":"replica-client-required-before-opening-database","errorCode":null,"errorMessage":"replica client required before opening database","messagePattern":"replica client required before opening database","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"db.go","lineNumber":785,"sourceCode":"}\n\n// Open initializes the background monitoring goroutine.\nfunc (db *DB) Open() (err error) {\n\tdb.mu.Lock()\n\tif db.opened {\n\t\tdb.mu.Unlock()\n\t\treturn nil // already open\n\t}\n\t// Recreate context for fresh start (handles reopen after close)\n\tdb.ctx, db.cancel = context.WithCancel(context.Background())\n\tdb.mu.Unlock()\n\n\t// Validate fields on database.\n\tif db.Replica == nil {\n\t\treturn fmt.Errorf(\"replica required before opening database\")\n\t}\n\tif db.Replica.Client == nil {\n\t\treturn fmt.Errorf(\"replica client required before opening database\")\n\t}\n\tif db.MinCheckpointPageN <= 0 {\n\t\treturn fmt.Errorf(\"minimum checkpoint page count required\")\n\t}\n\n\t// Clear old temporary files that my have been left from a crash.\n\tif err := removeTmpFiles(db.metaPath); err != nil {\n\t\treturn fmt.Errorf(\"cannot remove tmp files: %w\", err)\n\t}\n\n\t// Set the compactor client once before starting any goroutines.\n\tdb.compactor.VerifyCompaction = db.VerifyCompaction\n\tdb.compactor.RetentionEnabled = db.RetentionEnabled\n\tdb.compactor.client = db.Replica.Client\n\n\t// Start monitoring SQLite database in a separate goroutine.\n\tif db.MonitorInterval > 0 {\n\t\tdb.wg.Add(1)","sourceCodeStart":767,"sourceCodeEnd":803,"githubUrl":"https://github.com/benbjohnson/litestream/blob/4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3/db.go#L767-L803","documentation":"DB.Open validates that db.Replica.Client is non-nil after checking the Replica itself. The Replica struct exists but carries no storage backend implementation, so Litestream cannot know where to replicate LTX files. Like the previous check, it is a fail-fast precondition enforced at open time.","triggerScenarios":"Setting db.Replica = litestream.NewReplica(...) but never assigning db.Replica.Client; creating a Replica whose Client field was cleared or not deserialized from config.","commonSituations":"Library users wiring a replica manually and forgetting the backend client (S3, GCS, SFTP, file, etc.); custom ReplicaClient implementations assigned incorrectly; config parsing that created the replica but failed to instantiate the client silently.","solutions":["Assign a concrete replica client, e.g. db.Replica.Client = s3.NewReplicaClient() with its fields set.","For config-driven setups, verify the replica type in YAML maps to a registered backend and the client is built during config parsing.","Log/inspect the Replica object right before Open() to confirm Client != nil."],"exampleFix":"// before\ndb.Replica = litestream.NewReplica(db, \"s3\")\nif err := db.Open(); err != nil { ... }\n// after\ndb.Replica = litestream.NewReplica(db, \"s3\")\nrc := s3.NewReplicaClient()\nrc.Bucket, rc.Path, rc.Region = \"my-bucket\", \"db\", \"us-east-1\"\ndb.Replica.Client = rc\nif err := db.Open(); err != nil { ... }","handlingStrategy":"validation","validationCode":"if db.Replica != nil && db.Replica.Client == nil {\n    return fmt.Errorf(\"replica %s: client backend not configured\", db.Replica.Name())\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Construct replicas via factory functions (e.g. NewReplicaFromConfig) that always set Client.","Unit-test DB construction paths to assert Replica.Client != nil."],"tags":["database","replication","validation"],"backgroundTag":"missing-required-config-field","analyzedSha":"4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3","analyzedAt":"2026-09-06T18:29:25.564Z","contentChangedAt":"2026-09-06T18:29:25.564Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}