benbjohnson/litestream · error

new wal reader: %w

Error message

new wal reader: %w

What it means

NewWALReader failed to parse the WAL header when detectFullCheckpoint validates the WAL. Litestream requires a valid 32-byte WAL header (magic, format version, page size, checkpoint sequence); a malformed or empty header makes the reader unusable.

Source

Thrown at db.go:1879

}

// detectFullCheckpoint attempts to detect checks if a FULL or RESTART checkpoint
// has occurred and we may have missed some frames.
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

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Inspect the first 32 bytes of the -wal file to confirm valid SQLite WAL magic
  2. Trigger a checkpoint (PRAGMA wal_checkpoint(TRUNCATE)) or restart the app so SQLite rewrites a fresh WAL
  3. Restore the WAL file from backup if it is corrupt
  4. Verify the disk is not full / filesystem not corrupt

Example fix

// before
$ xxd /data/app.db-wal | head -1
00000000: 0000 0000 0000 0000 ...   (zeroed header)
// after
$ sqlite3 /data/app.db 'PRAGMA wal_checkpoint(TRUNCATE);'  # regenerates a valid WAL
Defensive patterns

Strategy: validation

Validate before calling

hdr, err := os.ReadFile(walPath)
valid := err == nil && len(hdr) >= 32 && (binary.BigEndian.Uint32(hdr[0:4]) == 0x377f0682 || binary.BigEndian.Uint32(hdr[0:4]) == 0x377f0683)

Try / catch

if err != nil {
	if strings.Contains(err.Error(), "new wal reader") {
		// WAL header corrupt: checkpoint via the app, then restart litestream
		run("sqlite3 "+dbPath+" 'PRAGMA wal_checkpoint(TRUNCATE);'")
	}
}

Prevention

When it happens

Trigger: NewWALReader(walFile, logger) returns an error: WAL file smaller than WALHeaderSize, invalid WAL magic bytes (not 0x377f0682/0x377f0683), or unsupported page size in the header.

Common situations: A non-SQLite or corrupted file at the WAL path; WAL truncated mid-header by a crash or disk-full condition; an SQLite version/pragma producing an unsupported WAL header; file zeroed by storage failure.

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/baca5922daadfa4f. Report an issue: GitHub.