benbjohnson/litestream · error

new wal reader, after reset

Error message

new wal reader, after reset

What it means

After a PrevFrameMismatchError, Litestream falls back to snapshotting by re-creating the reader from the WAL header; this error means even that fallback NewWALReader failed, so the WAL header itself is unreadable. Note the message omits %w, hiding the underlying cause.

Source

Thrown at db.go:2063

	if err != nil {
		return result, err
	}
	defer walFile.Close()

	walReaderLogger := db.Logger.With(LogKeySubsystem, LogSubsystemWALReader)
	var rd *WALReader
	if info.offset == WALHeaderSize {
		if rd, err = NewWALReader(walFile, walReaderLogger); err != nil {
			return result, fmt.Errorf("new wal reader: %w", err)
		}
	} else {
		// If we cannot verify the previous frame
		var pfmError *PrevFrameMismatchError
		if rd, err = NewWALReaderWithOffset(ctx, walFile, info.offset, info.salt1, info.salt2, walReaderLogger); errors.As(err, &pfmError) {
			db.Logger.Log(ctx, internal.LevelTrace, "prev frame mismatch, snapshotting", "err", pfmError.Err)
			info.offset = WALHeaderSize
			if rd, err = NewWALReader(walFile, walReaderLogger); err != nil {
				return result, fmt.Errorf("new wal reader, after reset")
			}
		} else if err != nil {
			return result, fmt.Errorf("new wal reader with offset: %w", err)
		}
	}

	// Build a mapping of changed page numbers and their latest content.
	db.setSyncDiagPhase(diagPhaseSyncPageMap,
		func(s *diagState) {
			s.txID = txID
			s.snapshotting = info.snapshotting
			s.reason = info.reason
		})
	if info.snapshotting {
		maxSyncWALBytes = 0
	}
	pageMap, maxOffset, walCommit, limited, err := rd.pageMap(ctx, maxSyncWALBytes)
	if err != nil {

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Retry the sync — a transient race with SQLite WAL restart usually resolves next iteration
  2. Inspect the WAL header bytes; if corrupt, force SQLite to rebuild it via PRAGMA wal_checkpoint(TRUNCATE)
  3. Check disk health if failures persist
  4. File an issue — this message drops the wrapped cause, so also consider improving it to include the underlying error

Example fix

// before
return result, fmt.Errorf("new wal reader, after reset")
// after
return result, fmt.Errorf("new wal reader, after reset: %w", err)
Defensive patterns

Strategy: retry

Validate before calling

// confirm WAL header is readable before assuming transient
f, err := os.Open(dbPath + "-wal")
if err == nil {
	hdr := make([]byte, 32)
	n, _ := f.Read(hdr)
	f.Close()
	if n < 32 { /* WAL mid-restart; wait and retry */ }
}

Try / catch

if err != nil && strings.Contains(err.Error(), "new wal reader, after reset") {
	time.Sleep(retryInterval) // likely raced with a WAL restart
	retrySync()
}

Prevention

When it happens

Trigger: Prev frame mismatch triggered the offset reset to WALHeaderSize, and the subsequent NewWALReader still failed — i.e. the WAL file's header region is corrupt/truncated/empty despite the offset-based reader having read far enough to hit a mismatch.

Common situations: WAL concurrently truncated to zero by a RESTART checkpoint between the two reader constructions; disk corruption affecting the header page; race where the -wal file is replaced mid-sync.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


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