benbjohnson/litestream · error

new ltx compactor: %w

Error message

new ltx compactor: %w

What it means

Hydrator.Restore constructs an ltx.NewCompactor over the opened readers; this error wraps a failure from that constructor. The LTX compactor validates its inputs (e.g. reader headers/ordering) before compacting, so a construction error means the reader set itself is invalid.

Source

Thrown at vfs.go:789

	for _, info := range infos {
		h.logger.Debug("opening ltx file for hydration", "level", info.Level, "min", info.MinTXID, "max", info.MaxTXID)
		rc, err := h.client.OpenLTXFile(ctx, info.Level, info.MinTXID, info.MaxTXID, 0, 0)
		if err != nil {
			return fmt.Errorf("open ltx file: %w", err)
		}
		rdrs = append(rdrs, rc)
	}

	if len(rdrs) == 0 {
		return fmt.Errorf("no ltx files for hydration")
	}

	// Compact and decode using io.Pipe pattern
	pr, pw := io.Pipe()
	c, err := ltx.NewCompactor(pw, rdrs)
	if err != nil {
		return fmt.Errorf("new ltx compactor: %w", err)
	}
	c.HeaderFlags = ltx.HeaderFlagNoChecksum
	h.compactor = c

	go func() {
		_ = pw.CloseWithError(c.Compact(ctx))
	}()

	h.mu.Lock()
	defer h.mu.Unlock()

	dec := ltx.NewDecoder(pr)
	if err := dec.DecodeDatabaseTo(h.file); err != nil {
		return fmt.Errorf("decode database: %w", err)
	}

	h.txid = infos[len(infos)-1].MaxTXID
	return nil

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Ensure at least one non-empty LTX reader is passed; check the infos list is non-empty before Restore.
  2. Verify all LTX files belong to the same database (matching page size, database ID) — re-list from the correct replica path.
  3. Re-download the offending file to rule out truncation/corruption; compare checksums with the replica listing.
  4. Update litestream/ltx to matching versions if files were written by a newer writer with different header flags.

Example fix

// before
c, err := ltx.NewCompactor(pw, rdrs)
// after
if len(rdrs) == 0 { return fmt.Errorf("cannot compact: no ltx readers") }
c, err := ltx.NewCompactor(pw, rdrs)
if err != nil { return fmt.Errorf("new ltx compactor: %w", err) }
Defensive patterns

Strategy: validation

Validate before calling

if len(rdrs) == 0 {
    return fmt.Errorf("compactor requires at least one ltx reader")
}
for _, info := range infos {
    if info.PageSize != expectedPageSize {
        return fmt.Errorf("page size mismatch for ltx %d", info.MaxTXID)
    }
}

Try / catch

c, err := ltx.NewCompactor(pw, rdrs)
if err != nil {
    return fmt.Errorf("invalid ltx reader set: %w", err)
}

Prevention

When it happens

Trigger: Calling Restore with an empty reader slice (NewCompactor requires at least one reader), or LTX files whose headers cannot be validated as a coherent ordered chain during compactor setup.

Common situations: A bug or stale cache passing zero-length/empty LTX files; mixing LTX files from different databases with conflicting page sizes or timestamps; corrupted download from the object store truncated the header.

Understand the failure class

Background: "must be a positive integer", "cannot be empty", "invalid argument": how invalid-argument errors work across open-source libraries — this error's family across 33 libraries.

Related errors


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