benbjohnson/litestream · critical · LTXError

stage-write

stage-write

Error message

%w: %w (ErrDiskFull)

What it means

While writing the LTX header during sync staging, the encoder write failed with a disk-full condition. Litestream detects ENOSPC-class errors via isDiskFullError and re-reports them as a structured LTXError with code "stage-write" wrapping ErrDiskFull. The local disk (not cloud storage) ran out of space while creating the LTX file.

Source

Thrown at db.go:2160

	enc, err := ltx.NewEncoder(ltxFile)
	if err != nil {
		return result, fmt.Errorf("new ltx encoder: %w", err)
	}
	if err := enc.EncodeHeader(ltx.Header{
		Version:   ltx.Version,
		Flags:     ltx.HeaderFlagNoChecksum,
		PageSize:  uint32(db.pageSize),
		Commit:    commit,
		MinTXID:   txID,
		MaxTXID:   txID,
		Timestamp: timestamp.UnixMilli(),
		WALOffset: info.offset,
		WALSize:   sz,
		WALSalt1:  rd.salt1,
		WALSalt2:  rd.salt2,
	}); err != nil {
		if isDiskFullError(err) {
			return result, NewLTXError("stage-write", tmpFilename, 0, uint64(txID), uint64(txID), fmt.Errorf("%w: %w", ErrDiskFull, err))
		}
		return result, fmt.Errorf("encode ltx header: %w", err)
	}

	// If we need a full snapshot, then copy from the database & WAL.
	// Otherwise, just copy incrementally from the WAL.
	if info.snapshotting {
		db.setSyncDiagPhase(diagPhaseWriteLTXFromDB,
			func(s *diagState) {
				s.txID = txID
				s.walSize = sz
				s.snapshotting = true
				s.reason = info.reason
			})
		if err := db.writeLTXFromDB(ctx, enc, walFile, commit, pageMap); err != nil {
			if isDiskFullError(err) {
				return result, NewLTXError("stage-write", tmpFilename, 0, uint64(txID), uint64(txID), fmt.Errorf("%w: %w", ErrDiskFull, err))
			}

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Free space on the volume holding the litestream data directory (df -h, delete stale files/logs).
  2. Move the litestream data/replica directory to a larger volume and update the config path.
  3. Raise the container/disk quota if running in Docker/Kubernetes.
  4. Enable the `auto-recover` replica option or run `litestream reset` for the DB after freeing space, since a failed staged LTX can leave local state requiring reset.
  5. Add monitoring/alerting on disk usage (e.g. df threshold) so replication never races ENOSPC.

Example fix

// before
cf.yml: litestream data on 5GB volume, db is 8GB -> ENOSPC at stage-write
// after
# move data dir to larger volume and update config
path: /data/litestream/db   # /data is a 100GB volume
Defensive patterns

Strategy: validation

Validate before calling

func assertDiskHeadroom(path string, needBytes uint64) error {
    var st syscall.Statfs_t
    if err := syscall.Statfs(path, &st); err != nil { return err }
    free := uint64(st.Bavail) * uint64(st.Bsize)
    if free < needBytes {
        return fmt.Errorf("only %d bytes free at %s, need %d", free, path, needBytes)
    }
    return null_or_nil()
}

Try / catch

if errors.Is(err, ErrDiskFull) {
    alertDiskFull(filepath.Dir(stagingPath))
    // free space / expand volume, then restart litestream or litestream reset
}

Prevention

When it happens

Trigger: Replicating a large transaction (snapshot or big WAL frame set) when the volume holding the litestream data directory has insufficient free space for even the LTX header write.

Common situations: Small container/disk quota for /var/lib/litestream; snapshot of a multi-GB database on a nearly full disk; log files or core dumps consuming the same volume; Docker overlay limit reached.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


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