{"record":{"id":"cddcc33343633a47","repo":"benbjohnson/litestream","slug":"open-db-w","errorCode":null,"errorMessage":"open db: %w","messagePattern":"open db: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"store.go","lineNumber":317,"sourceCode":"\t\t\ts.mu.Unlock()\n\t\t\treturn nil\n\t\t}\n\t}\n\ts.mu.Unlock()\n\n\t// Apply store-wide settings before opening the database.\n\tdb.SetLogger(s.Logger.With(LogKeyDB, filepath.Base(db.Path())))\n\tdb.L0Retention = s.L0Retention\n\tdb.ShutdownSyncTimeout = s.ShutdownSyncTimeout\n\tdb.ShutdownSyncInterval = s.ShutdownSyncInterval\n\tdb.VerifyCompaction = s.VerifyCompaction\n\tdb.RetentionEnabled = s.RetentionEnabled\n\tdb.Done = s.done\n\n\t// Open the database without holding the lock to avoid blocking other operations.\n\t// The double-check pattern below handles the race condition.\n\tif err := db.Open(); err != nil {\n\t\treturn fmt.Errorf(\"open db: %w\", err)\n\t}\n\n\t// Second check: verify database wasn't added by another goroutine while we were opening.\n\t// If it was, close our instance and return without error.\n\ts.mu.Lock()\n\n\tfor _, existing := range s.dbs {\n\t\tif existing.Path() == db.Path() {\n\t\t\t// Another goroutine added this database while we were opening.\n\t\t\t// Release lock before closing to avoid potential deadlock.\n\t\t\ts.mu.Unlock()\n\t\t\tif err := db.Close(context.Background()); err != nil {\n\t\t\t\tdb.Logger.Error(\"close duplicate db\", \"path\", db.Path(), \"error\", err)\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n","sourceCodeStart":299,"sourceCodeEnd":335,"githubUrl":"https://github.com/benbjohnson/litestream/blob/4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3/store.go#L299-L335","documentation":"Store.RegisterDB opens the database (db.Open) outside the store lock; any failure is wrapped as \"open db: %w\". The cause is whatever db.Open hit — typically the SQLite file is missing, unreadable, corrupt, or the WAL/SHM cannot be created. Because this is a disaster-recovery tool the open failure is returned to the caller rather than swallowed.","triggerScenarios":"RegisterDB is called (directly or via handleRegister / handlePotentialDatabase) and db.Open fails: file does not exist, permission denied on db or its -wal/-shm files, SQLite file corruption, directory missing, or disk I/O error.","commonSituations":"Pointing litestream at a path where the application has not created the SQLite file yet; running litestream as a different user than the app; container volume not mounted; WAL file left unwritable after permission changes.","solutions":["Unwrap the error to see the underlying cause (os.PathError / SQLite error code)","Verify the database file exists at the configured path and the process has read/write access to the db, -wal, and -shm files","Ensure the application has created the database before registering it (or create it with sqlite3 first)","Check disk space and volume mount status"],"exampleFix":"// before\nlitestream.yml: dbpath: /data/app.db  # file not created yet\n\n// after\n$ sqlite3 /data/app.db \"PRAGMA journal_mode=WAL; SELECT 1;\"\n$ # then start litestream","handlingStrategy":"fallback","validationCode":"info, err := os.Stat(dbPath)\nif err != nil { return err } // missing file\nif info.IsDir() { return fmt.Errorf(\"%s is a directory\", dbPath) }\nif f, err := os.OpenFile(dbPath, os.O_RDWR, 0); err != nil { return err } else { f.Close() }","typeGuard":null,"tryCatchPattern":"if err := store.RegisterDB(db); err != nil {\n    if strings.HasPrefix(err.Error(), \"open db:\") {\n        // unwrap cause: missing file, permissions, or corruption; fix environment, don't blind-retry\n        log.Printf(\"db open failed: %v\", err)\n    }\n}","preventionTips":["Verify the SQLite file exists before starting litestream","Run litestream under a user with read/write access to the db, -wal, and -shm files","Confirm volume mounts and disk space in containers","Create the database with WAL journal mode before registration"],"tags":["sqlite","open","file","startup"],"backgroundTag":"file-open-failed","analyzedSha":"4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3","analyzedAt":"2026-09-06T18:29:25.564Z","contentChangedAt":"2026-09-06T18:29:25.564Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}