benbjohnson/litestream · error

frame salts until: %w

Error message

frame salts until: %w

What it means

WALReader.FrameSaltsUntil failed while scanning WAL frame salts to detect whether a FULL/RESTART checkpoint occurred. Litestream compares frame salts against its known salts; an I/O or parse error mid-scan aborts detection and is wrapped and returned to the sync loop.

Source

Thrown at db.go:1883

func (db *DB) detectFullCheckpoint(ctx context.Context, knownSalts [][2]uint32) (bool, error) {
	walFile, err := os.Open(db.WALPath())
	if err != nil {
		return false, fmt.Errorf("open wal file: %w", err)
	}
	defer walFile.Close()

	var lastKnownSalt [2]uint32
	if len(knownSalts) > 0 {
		lastKnownSalt = knownSalts[len(knownSalts)-1]
	}

	rd, err := NewWALReader(walFile, db.Logger.With(LogKeySubsystem, LogSubsystemWALReader))
	if err != nil {
		return false, fmt.Errorf("new wal reader: %w", err)
	}
	m, err := rd.FrameSaltsUntil(ctx, lastKnownSalt)
	if err != nil {
		return false, fmt.Errorf("frame salts until: %w", err)
	}

	// Remove known salts from the map.
	for _, salt := range knownSalts {
		delete(m, salt)
	}

	// If we have more than one unknown salt, then we have a FULL or RESTART checkpoint.
	return len(m) >= 1, nil
}

type syncInfo struct {
	offset              int64 // end of the previous LTX read
	salt1               uint32
	salt2               uint32
	prevCommit          uint32
	snapshotting        bool   // if true, a full snapshot is required
	reason              string // reason for snapshot

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Retry the sync — transient races with SQLite checkpointing usually resolve on the next iteration
  2. Check for context cancellation from shutdown and treat it as a normal stop, not an incident
  3. Run filesystem/disk health checks if errors persist
  4. Restart Litestream to rebuild known salts after a WAL restart (salts change on RESTART checkpoints)
Defensive patterns

Strategy: retry

Validate before calling

// no pre-call check possible; ensure ctx is live and WAL exists
select {
case <-ctx.Done():
	return ctx.Err()
default:
}

Try / catch

err := db.SyncAndWait(ctx)
if isCancellation(err) {
	return nil // normal shutdown path
}
if err != nil {
	scheduleRetryWithBackoff(err)
}

Prevention

When it happens

Trigger: rd.FrameSaltsUntil(ctx, lastKnownSalt) errors: read failure partway through the WAL (partial frame, short read), context cancellation, or a frame that fails header validation.

Common situations: WAL file being rewritten concurrently by SQLite checkpoint while Litestream reads it; disk I/O errors on failing hardware; ctx cancelled during shutdown; WAL truncated between open and scan.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


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