benbjohnson/litestream · error

prev WAL offset is less than the header size: %d

Error message

prev WAL offset is less than the header size: %d

What it means

The previously synced WAL offset stored in sync state is smaller than the 32-byte WAL header, which is structurally impossible for a valid WAL position. verify rejects it rather than reading a frame at an invalid offset.

Source

Thrown at db.go:1784

		}
		info.reason = "wal header salt reset, snapshotting"
		return info, nil
	}

	// If offset is at the beginning of the first page, we can't check for previous page.
	prevWALOffset := info.offset - frameSize
	db.Logger.Debug("verify", "saltMatch", saltMatch, "prevWALOffset", prevWALOffset)

	if prevWALOffset == WALHeaderSize {
		if saltMatch { // No writes occurred since last sync, salt still matches
			info.snapshotting = false
			return info, nil
		}
		// Salt has changed but we don't know if writes occurred since last sync
		info.reason = "wal header salt reset, snapshotting"
		return info, nil
	} else if prevWALOffset < WALHeaderSize {
		return info, fmt.Errorf("prev WAL offset is less than the header size: %d", prevWALOffset)
	}

	// If we can't verify the last page is in the last LTX file, then we need to snapshot.
	lastPageMatch, err := db.lastPageMatch(ctx, dec, prevWALOffset, frameSize)
	if err != nil {
		return info, fmt.Errorf("last page match: %w", err)
	} else if !lastPageMatch {
		info.reason = "last page does not exist in last ltx file, wal overwritten by another process"
		return info, nil
	}

	db.Logger.Debug("verify.2", "lastPageMatch", lastPageMatch)

	// Salt has changed which could indicate a FULL checkpoint.
	// If we have a last page match, then we can assume that the WAL has not been overwritten.
	if !saltMatch {
		db.Logger.Log(ctx, internal.LevelTrace, "wal restarted",
			"salt1", salt1,

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Run `litestream reset <db>` to discard local state and resnapshot from the replica
  2. Inspect the LTX file's WALHeader.WALOffset for the offending value (`litestream ltx`)
  3. Avoid manually editing or truncating LTX files; always restore via litestream
Defensive patterns

Strategy: fallback

Validate before calling

// sanity-check any restored LTX state
if hdr.WALOffset != 0 && hdr.WALOffset < 32 { /* invalid state: reset */ }

Type guard

func validWALOffset(off int64) bool { return off == 0 || off >= 32 }

Try / catch

if err := db.Sync(ctx); err != nil {
    if strings.Contains(err.Error(), "less than the header size") {
        // local state invalid: litestream reset and resnapshot
    }
}

Prevention

When it happens

Trigger: prevWALOffset (from the last sync's recorded state) is > 0 but < WALHeaderSize — corrupted or hand-edited sync state, or a zero/truncated LTX-derived offset fed into verify.

Common situations: Manually editing or partially restoring LTX/replica state; corruption of local offset bookkeeping; restoring an LTX file from an incompatible litestream version.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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