benbjohnson/litestream · error
read position before sync: %w
Error message
read position before sync: %w
What it means
Store.SyncDB failed before performing the sync because it could not read the database's current replication position via db.MaxLTX(). The wrapped error (ErrDatabaseNotOpen, storage errors, or internal position-lookup failures) is preserved with %w. SyncDB reports this position so callers can compare before/after TXIDs, so any failure reading it aborts the whole call.
Source
Thrown at store.go:445
// SyncDB forces an immediate sync for a database. If wait is true, blocks
// until both WAL-to-LTX and LTX-to-remote sync complete. If wait is false,
// only performs the WAL-to-LTX sync and lets the replica monitor handle upload.
// Lock waits are context-aware: the timeout is honored while waiting for
// the database sync executor and the replica sync lock.
func (s *Store) SyncDB(ctx context.Context, path string, wait bool) (SyncDBResult, error) {
db := s.FindDB(path)
if db == nil {
return SyncDBResult{}, fmt.Errorf("%w: %s", ErrDatabaseNotFound, path)
}
if !db.IsOpen() {
return SyncDBResult{}, fmt.Errorf("%w: %s", ErrDatabaseNotOpen, path)
}
_, beforeTXID, err := db.MaxLTX()
if err != nil {
return SyncDBResult{}, fmt.Errorf("read position before sync: %w", err)
}
if wait {
if err := db.SyncAndWait(ctx); err != nil {
return SyncDBResult{}, fmt.Errorf("sync database: %w", err)
}
} else {
if err := db.Sync(ctx); err != nil {
return SyncDBResult{}, fmt.Errorf("sync database: %w", err)
}
}
_, afterTXID, err := db.MaxLTX()
if err != nil {
return SyncDBResult{}, fmt.Errorf("read position after sync: %w", err)
}
var replicatedTXID uint64View on GitHub (pinned to 4ed7a308f6)
Solutions
- Verify the database path passed to SyncDB exactly matches a path registered with the Store (check Store output of running DBs).
- Call SyncDB only after the Store has fully started and opened the database; wait for startup or use the Store's DB lookup API.
- Check that the database was not closed/unregistered concurrently; serialize calls or hold a reference to the *DB.
- Inspect the wrapped error (errors.Is(err, ErrDatabaseNotOpen)) to distinguish not-open from an internal MaxLTX failure.
Example fix
// before
res, err := store.SyncDB(ctx, "/data/mydb.sqlite", true)
// after
if err := store.OpenDatabase(ctx, "/data/mydb.sqlite"); err != nil { return err }
res, err := store.SyncDB(ctx, "/data/mydb.sqlite", true)
if err != nil && errors.Is(err, litestream.ErrDatabaseNotOpen) {
// path not open: look up correct path
} Defensive patterns
Strategy: validation
Validate before calling
if store == nil || path == "" {
return fmt.Errorf("store and db path required")
}
// look up DB in store before syncing
if _, err := store.DB(context.Background(), path); err != nil {
return fmt.Errorf("db %q not registered: %w", path, err)
} Type guard
func isOpen(store *litestream.Store, path string) bool {
db, err := store.DB(context.Background(), path)
return err == nil && db != nil && db.SyncStatus() != litestream.SyncStatusUnknown
} Try / catch
res, err := store.SyncDB(ctx, path, true)
if err != nil {
if errors.Is(err, litestream.ErrDatabaseNotOpen) {
return fmt.Errorf("db %q is not open: register it with the store first", path)
}
return err
} Prevention
- Always obtain the path from the same config/registration used to create the DB
- Check errors.Is(err, litestream.ErrDatabaseNotOpen) to distinguish not-open from internal failures
- Avoid calling SyncDB during Store shutdown
- Keep a *DB reference instead of re-resolving by path
When it happens
Trigger: Store.SyncDB(ctx, path, wait) is called with a path that is not registered/open in the Store (db.IsOpen() false -> ErrDatabaseNotOpen), or MaxLTX() fails while consulting the local LTX position metadata.
Common situations: Calling SyncDB with a database path that was never registered via OpenDB/Config, after the DB was closed or removed from the store, a typo in the path, or racing SyncDB against Store shutdown.
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
- sync database: %w
- read position after sync: %w
- cannot determine current position: %w
- fetch dst level info: %w
- fetch db position: %w
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/54e69ed62b1363fa.
Report an issue: GitHub.