benbjohnson/litestream · error

local position: %w

Error message

local position: %w

What it means

DB.SyncStatus() wraps any error returned by db.Pos() with the prefix "local position". db.Pos() reads the current WAL position (TXID) of the local database, so this means the local database's position could not be read — typically because the DB is not open, the WAL/SHM files are unreadable, or the underlying SQLite query failed. It is a wrapper, so the root cause is in the wrapped error.

Source

Thrown at db.go:699

// SyncStatus represents the current replication state of the database.
type SyncStatus struct {
	LocalTXID  ltx.TXID
	RemoteTXID ltx.TXID
	InSync     bool
}

// SyncStatus returns the current replication status of the database, comparing
// the local transaction position against the remote replica position. The remote
// position is queried from the replica storage, so this method may perform I/O.
func (db *DB) SyncStatus(ctx context.Context) (SyncStatus, error) {
	if db.Replica == nil {
		return SyncStatus{}, fmt.Errorf("no replica configured")
	}

	localPos, err := db.Pos()
	if err != nil {
		return SyncStatus{}, fmt.Errorf("local position: %w", err)
	}

	remotePos, err := db.Replica.calcPos(ctx)
	if err != nil {
		return SyncStatus{}, fmt.Errorf("remote position: %w", err)
	}

	return SyncStatus{
		LocalTXID:  localPos.TXID,
		RemoteTXID: remotePos.TXID,
		InSync:     localPos.TXID > 0 && localPos.TXID == remotePos.TXID,
	}, nil
}

// SyncAndWait performs a full sync: WAL to LTX files, then LTX files to remote
// replica. Blocks until both stages complete.
func (db *DB) SyncAndWait(ctx context.Context) error {
	if db.Replica == nil {

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Inspect the wrapped %w error to find the root cause (it is chained, so errors.Is/errors.As work).
  2. Ensure db.Open(ctx) completed successfully before calling SyncStatus().
  3. Check that the database path exists and the process has read permissions on the db, WAL, and SHM files.
  4. If the local state is corrupted, consider litestream reset (or the reset API) to clear local LTX state and restore.

Example fix

// before
status, err := db.SyncStatus(ctx) // fails if DB not open
// after
if err := db.Open(ctx); err != nil {
    return fmt.Errorf("open db: %w", err)
}
status, err := db.SyncStatus(ctx)
if err != nil {
    return fmt.Errorf("sync status: %w", err) // inspect wrapped cause
}
Defensive patterns

Strategy: try-catch

Validate before calling

if _, err := os.Stat(db.Path()); err != nil {
    // db file missing; expect position read failure
}
// ensure Open() has completed before calling SyncStatus

Try / catch

status, err := db.SyncStatus(ctx)
if err != nil {
    var root error
    errors.As(err, &root) // or errors.Is on known causes
    return fmt.Errorf("sync status unavailable: %w", err)
}

Prevention

When it happens

Trigger: Calling db.SyncStatus(ctx) when db.Pos() fails: the DB was not opened (Open() not called or failed), the database file or WAL is corrupted/unreadable, or the SQLite connection used to read the position errors out.

Common situations: Status dashboards or health checks polling SyncStatus() before Open() finished; the database file was deleted or locked mid-run; disk I/O errors on the WAL file; calling SyncStatus on a DB struct constructed manually rather than via NewDB/Open.

Understand the failure class

Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.

Related errors


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