benbjohnson/litestream · error
truncate: %w
Error message
truncate: %w
What it means
applyLTXFile truncates the database file to hdr.Commit * pageSize bytes to match the committed size from the LTX transaction header. Failure here means the OS rejected the truncate (f.Truncate), leaving the local file at the wrong size, so the transaction cannot be considered applied.
Source
Thrown at replica.go:991
if phdr.Pgno == 1 && len(data) >= 28 {
data[18], data[19] = 0x01, 0x01
_, _ = rand.Read(data[24:28])
}
off := int64(phdr.Pgno-1) * int64(pageSize)
if _, err := f.WriteAt(data, off); err != nil {
return fmt.Errorf("write page %d: %w", phdr.Pgno, err)
}
}
if hdr.Commit > 0 {
if err := f.Sync(); err != nil {
return fmt.Errorf("sync before truncate: %w", err)
}
newSize := int64(hdr.Commit) * int64(pageSize)
if err := f.Truncate(newSize); err != nil {
return fmt.Errorf("truncate: %w", err)
}
}
if err := dec.Close(); err != nil {
return fmt.Errorf("close decoder: %w", err)
}
return f.Sync()
}
// fillFollowGap attempts to bridge a gap in level 0 files by searching
// higher compaction levels for a file that covers the missing TXID range.
func (r *Replica) fillFollowGap(ctx context.Context, f *os.File, afterTXID ltx.TXID, gapMinTXID ltx.TXID, pageSize uint32) (ltx.TXID, error) {
currentTXID := afterTXID
for level := 1; level < SnapshotLevel; level++ {
itr, err := r.Client.LTXFiles(ctx, level, 0, false)
if err != nil {View on GitHub (pinned to 4ed7a308f6)
Solutions
- Ensure the database file and its directory are writable by the litestream user (chown/chmod)
- Verify the volume is mounted read-write and has room for the target size
- Validate the LTX file header (hdr.Commit) is sane; reset corrupted state with `litestream reset <db>`
- Re-run restore/replication after fixing permissions or space
Example fix
# before $ ls -l db.sqlite -r--r--r-- 1 root root ... db.sqlite # after $ sudo chmod u+w db.sqlite && sudo chown litestream:litestream db.sqlite
Defensive patterns
Strategy: validation
Validate before calling
fi, err := os.Stat(dbPath)
if err != nil { return err }
if fi.Mode().Perm()&0200 == 0 {
return fmt.Errorf("db file %s is not writable", dbPath)
}
if fi.Size() > int64(hdr.Commit)*int64(pageSize) {
// shrink truncate expected; ensure space/perm, otherwise file may be locked read-only
return fmt.Errorf("truncate target %d < current %d; check file state", int64(hdr.Commit)*int64(pageSize), fi.Size())
} Try / catch
if err := r.applyLTXFile(ctx, f, info, pageSize); err != nil {
if errors.Is(err, syscall.EACCES) || errors.Is(err, syscall.EROFS) {
return fmt.Errorf("cannot truncate %s: fix permissions/mount: %w", dbPath, err)
}
return err
} Prevention
- Ensure litestream runs as a user with write access to the DB file
- Check mount options (rw, not ro) after crash recovery
- Size volumes for the committed database size plus headroom
- Validate LTX headers if commit sizes look implausible
When it happens
Trigger: f.Truncate(newSize) fails: read-only filesystem, permission denied on the file, newSize beyond filesystem/device limits, or an invalid hdr.Commit/pageSize combination producing an out-of-range size.
Common situations: Database file made read-only after a crash, running litestream as a user without write access, tiny tmpfs/disk where the committed size exceeds available space, corrupted LTX header yielding a nonsensical commit size.
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
- disk full
- cannot access output path: %w
- cannot access SQLite sidecar path: %w
- remove existing output path: %w
- remove ltx directory: %w
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/dbdb974c03503f6c.
Report an issue: GitHub.