benbjohnson/litestream · error

read txid file: %w

Error message

read txid file: %w

What it means

ReadTXIDFile reads the '<outputPath>-txid' sidecar file to recover the last applied TXID. A missing file is treated as first-run (returns 0, nil), but any other read error is wrapped as 'read txid file: %w'. This indicates the TXID file exists but could not be read, so the replica cannot determine its resume point.

Source

Thrown at replica.go:1762

		return fmt.Errorf("rename txid file: %w", err)
	}

	if err := internal.FsyncDir(filepath.Dir(txidPath)); err != nil {
		return fmt.Errorf("sync txid dir: %w", err)
	}
	return nil
}

// ReadTXIDFile reads the TXID from a sidecar file at <outputPath>-txid.
// Returns 0, nil if the file does not exist (first run).
func ReadTXIDFile(outputPath string) (ltx.TXID, error) {
	txidPath := TXIDPath(outputPath)

	data, err := os.ReadFile(txidPath)
	if os.IsNotExist(err) {
		return 0, nil
	} else if err != nil {
		return 0, fmt.Errorf("read txid file: %w", err)
	}

	txid, err := ltx.ParseTXID(strings.TrimSpace(string(data)))
	if err != nil {
		return 0, fmt.Errorf("parse txid file: %w", err)
	}

	return txid, nil
}

// ValidationError represents a single validation issue.
type ValidationError struct {
	Level    int           // compaction level
	Type     string        // "gap", "overlap", or "unsorted"
	Message  string        // human-readable description
	PrevFile *ltx.FileInfo // previous file
	CurrFile *ltx.FileInfo // current file that caused error
}

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Fix permissions on the '<outputPath>-txid' file so the litestream user can read it (chown/chmod)
  2. If the path is a directory or corrupted beyond repair, remove it and let litestream re-create it via a fresh restore/sync
  3. Run litestream as the same user that owns the database and sidecar files
  4. Check device health if dmesg shows I/O errors on the volume

Example fix

# before
-rw------- root root my.db-txid   # litestream runs as litestream user
# after
chown litestream:litestream my.db-txid && chmod 644 my.db-txid
Defensive patterns

Strategy: validation

Validate before calling

txidPath := TXIDPath(outputPath)
if st, err := os.Stat(txidPath); err == nil && st.IsDir() {
    return fmt.Errorf("%s is a directory; remove it", txidPath)
}
if info, err := os.Stat(txidPath); err == nil {
    f, _ := os.Open(txidPath)
    defer f.Close()
    if _, err := f.Read(make([]byte, 1)); err != nil {
        return fmt.Errorf("txid file unreadable: %w", err)
    }
}

Type guard

func txidFileReadable(path string) bool {
    f, err := os.Open(path)
    if err != nil { return false }
    defer f.Close()
    buf := make([]byte, 1)
    _, err = f.Read(buf)
    return err == nil
}

Try / catch

txid, err := ReadTXIDFile(outputPath)
if err != nil {
    if errors.Is(err, os.ErrPermission) {
        // fix ownership/permissions or run as file owner, then retry
    }
    return err
}

Prevention

When it happens

Trigger: os.ReadFile on '<outputPath>-txid' failing with EACCES (wrong permissions/owner), EISDIR (a directory occupies the path), or device I/O errors — while the file (or same-named directory) exists.

Common situations: TXID file created by root but litestream running as another user; leftover directory named 'my.db-txid' from a misconfiguration; corrupted storage; changed umask or container user after an upgrade.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


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