benbjohnson/litestream · critical · ErrLTXCorrupted

verify

Error message

verify

What it means

During restore/apply, each LTX file is verified with the LTX decoder before applying. If dec.Verify() fails, the error is wrapped by NewLTXError with op "verify" and tagged ErrLTXCorrupted. It means the local LTX file failed checksum/structural verification and cannot be trusted.

Source

Thrown at db.go:634

	}

	minTXID, maxTXID, err := db.MaxLTX()
	if err != nil {
		return ltx.Pos{}, err
	} else if minTXID == 0 {
		return ltx.Pos{}, nil // no replication yet
	}

	ltxPath := db.LTXPath(0, minTXID, maxTXID)
	f, err := os.Open(ltxPath)
	if err != nil {
		return ltx.Pos{}, NewLTXError("open", ltxPath, 0, uint64(minTXID), uint64(maxTXID), err)
	}
	defer func() { _ = f.Close() }()

	dec := ltx.NewDecoder(f)
	if err := dec.Verify(); err != nil {
		return ltx.Pos{}, NewLTXError("verify", ltxPath, 0, uint64(minTXID), uint64(maxTXID), fmt.Errorf("%w: %w", ErrLTXCorrupted, err))
	}

	pos := dec.PostApplyPos()
	db.pos.value = &pos

	return pos, nil
}

// invalidatePosCache clears the cached position so the next call to Pos()
// recomputes it from disk. Call this when L0 LTX files are deleted or
// when the L0 directory is cleared.
func (db *DB) invalidatePosCache() {
	db.pos.Lock()
	db.pos.value = nil
	db.pos.Unlock()
}

// Notify returns a channel that closes when the shadow WAL changes.

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Run litestream reset on the database to clear corrupted local LTX state, then re-replicate
  2. Verify remote replicas still hold intact files and restore from a replica instead of local cache
  3. Check host disk health (SMART) and free space; fix the corruption source
  4. If remote files are also corrupt, restore from the latest valid backup point before the corruption
  5. Enable auto-recover replica option to automatically reset local state on LTX errors

Example fix

// before: restore keeps failing on corrupt local state
$ litestream restore -o db.sqlite /path/to/db
// verify: ltx corrupted
// after: clear local state and retry
$ litestream reset /path/to/db
$ litestream restore -o db.sqlite /path/to/db
Defensive patterns

Strategy: fallback

Validate before calling

// Verify LTX integrity outside the apply path before restore
dec := ltx.NewDecoder(f)
if err := dec.Verify(); err != nil {
    return fmt.Errorf("%w: %w", ErrLTXCorrupted, err)
}

Try / catch

pos, err := db.restoreApplyLTX(ltxPath, minTXID, maxTXID)
if errors.Is(err, ErrLTXCorrupted) {
    // fall back: litestream reset + restore from remote replica
}

Prevention

When it happens

Trigger: Applying or restoring from an LTX file whose bytes were corrupted — truncated download, bit rot on disk, partial write during crash, or tampering; dec.Verify() returns non-nil and db.go wraps it as op=verify with minTXID/maxTXID context.

Common situations: Disk full during replication producing truncated files; faulty storage medium; interrupted network copy of the database directory; manually copied/partially synced LTX directories between hosts.

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