benbjohnson/litestream · critical · LTXError
stage-open
stage-open
Error message
%w: %w (ErrDiskFull)
What it means
Opening the temporary LTX file (<filename>.tmp) for read-write create failed with a disk-full condition, wrapped in an LTXError with code stage-open and classified as ErrDiskFull. Creation can also consume metadata blocks even if the WAL data would fit.
Source
Thrown at db.go:2123
// Exit if we have no new WAL pages and we aren't snapshotting.
if !info.snapshotting && sz == 0 {
db.Logger.Log(ctx, internal.LevelTrace, "sync: skip", "reason", "no new wal pages")
return result, nil
}
tmpFilename := filename + ".tmp"
if err := internal.MkdirAll(filepath.Dir(tmpFilename), db.dirInfo); err != nil {
if isDiskFullError(err) {
return result, NewLTXError("stage-mkdir", tmpFilename, 0, uint64(txID), uint64(txID), fmt.Errorf("%w: %w", ErrDiskFull, err))
}
return result, err
}
ltxFile, err := db.openLTXFile(tmpFilename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode)
if err != nil {
if isDiskFullError(err) {
return result, NewLTXError("stage-open", tmpFilename, 0, uint64(txID), uint64(txID), fmt.Errorf("%w: %w", ErrDiskFull, err))
}
return result, fmt.Errorf("open temp ltx file: %w", err)
}
defer func() { _ = os.Remove(tmpFilename) }()
defer func() { _ = ltxFile.Close() }()
uid, gid := internal.Fileinfo(db.fileInfo)
_ = os.Chown(tmpFilename, uid, gid)
db.Logger.Log(ctx, internal.LevelTrace, "encode header",
"txid", txID.String(),
"commit", commit,
"walOffset", info.offset,
"walSize", sz,
"salt1", rd.salt1,
"salt2", rd.salt2)
timestamp := time.Now()View on GitHub (pinned to 4ed7a308f6)
Solutions
- Free disk space (or expand the volume) on the staging directory's filesystem
- Check inode usage (df -i) and quotas, not just bytes
- Move the Litestream staging directory to larger storage via config
- Retry the sync after space is reclaimed; no reset required
Example fix
// before $ df -i /var/lib/litestream # inodes exhausted // after $ find /var/lib/litestream/tmp -type f -delete # or raise quota/inodes $ systemctl restart litestream
Defensive patterns
Strategy: fallback
Validate before calling
var st syscall.Statfs_t
if err := syscall.Statfs(stageDir, &st); err == nil {
if st.Bavail*uint64(st.Bsize) < minFreeBytes || st.Ffree < minFreeInodes {
return errors.New("insufficient disk/inodes for staging")
}
} Try / catch
var ltxErr *litestream.LTXError
if errors.As(err, <xErr) && errors.Is(ltxErr, litestream.ErrDiskFull) && ltxErr.Code == "stage-open" {
freeDiskOrExpandVolume()
nextSyncWillRetry() // no reset needed
} Prevention
- Monitor both bytes and inodes on the staging volume (df and df -i)
- Keep headroom for temp LTX files roughly the size of the WAL
- Enforce disk alerts in the same runbook as replication alerts
When it happens
Trigger: db.openLTXFile(tmpFilename, O_RDWR|O_CREATE|O_TRUNC) returns ENOSPC (via isDiskFullError) while creating the temp file in the staging directory.
Common situations: Disk filled between the mkdir and open steps on a nearly-full volume; inode/quota exhaustion manifesting as ENOSPC; container storage limits hit during a large snapshot stage.
Understand the failure class
Background: "File too large" / "file size exceeds limit" errors: why libraries cap file sizes and how to fix them — this error's family across 46 libraries.
Related errors
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/a87dbc70219a1631.
Report an issue: GitHub.