benbjohnson/litestream · critical
upload LTX: %w
Error message
upload LTX: %w
What it means
syncToRemoteWithLock built an LTX file from the dirty pages and failed to upload it to the replica via client.WriteLTXFile. The dirty pages remain dirty and pendingTXID is not advanced, so no data was committed remotely; the wrapped error is the storage client's failure (network, credentials, permissions, conflict).
Source
Thrown at vfs.go:2012
// Double-check dirty pages exist
if len(f.dirty) == 0 {
return nil
}
ctx := f.ctx
// Check for conflicts
if err := f.checkForConflict(ctx); err != nil {
return err
}
// Create LTX file from dirty pages
ltxReader := f.createLTXFromDirty()
// Upload LTX file to remote
info, err := f.client.WriteLTXFile(ctx, 0, f.pendingTXID, f.pendingTXID, ltxReader)
if err != nil {
return fmt.Errorf("upload LTX: %w", err)
}
f.logger.Info("synced to remote",
"txid", info.MaxTXID,
"pages", len(f.dirty),
"size", info.Size)
f.expectedTXID = f.pendingTXID
f.pendingTXID++
f.pos = ltx.Pos{TXID: f.expectedTXID}
if f.vfs != nil {
f.vfs.writeMu.Lock()
if f.expectedTXID > f.vfs.lastSyncedTXID {
f.vfs.lastSyncedTXID = f.expectedTXID
}
f.vfs.writeMu.Unlock()
}View on GitHub (pinned to 4ed7a308f6)
Solutions
- Check the wrapped cause: fix network egress, refresh credentials, or correct bucket IAM permissions.
- If it's a TXID conflict, ensure only one writer replicates per database (use leasing) or litestream reset local state and re-enable.
- Retry after transient failures — dirty state is preserved and the next sync tick will retry automatically.
- Verify replica client config (endpoint, region, bucket) with litestream ltx listing against the same bucket.
- Increase storage request timeouts / check rate limits if errors are throttling responses.
Example fix
// before client := s3.NewReplicaClient() // uses expired env credentials // after client = s3.NewReplicaClient() client.AccessKeyID, client.SecretAccessKey = refreshedKeys() // rotate before sync
Defensive patterns
Strategy: retry
Validate before calling
// Go: pre-flight replica write check before enabling write support
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
itr, err := client.LTXFiles(ctx, 0, 0, false)
if err != nil { return fmt.Errorf("replica not reachable: %w", err) }
itr.Close() Try / catch
if err := file.Sync(); err != nil {
if strings.HasPrefix(err.Error(), "upload LTX:") {
if errors.Is(err, context.DeadlineExceeded) || isTransient(err) {
time.Sleep(backoff) // dirty pages preserved; next tick retries
}
}
} Prevention
- Monitor credential expiry and rotate without downtime.
- Enforce single-writer (leasing) to prevent TXID conflicts.
- Alert on periodic syncLoop 'periodic sync failed' log lines.
- Verify bucket IAM allows PutObject and check egress firewall rules.
When it happens
Trigger: Sync() during transactions-off periods, periodic syncLoop tick, or the final sync in SetWriteEnabled(false), whenever the replica client cannot write the LTX file: network outage, expired AWS/S3 credentials, bucket permissions, or a TXID conflict detected just before upload.
Common situations: S3/Cloudflare R2 outages or throttling; IAM policy revoking PutObject on the bucket; rotating credentials without restarting litestream; concurrent writer from another node advancing remote TXID (competing writers without leasing); firewall blocking egress to the storage endpoint.
Understand the failure class
Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.
Related errors
- write ltx file: %w
- list level %d ltx files: %w
- list generations: %w
- list snapshots for generation %s: %w
- s3: put object %s: %w
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/e873bee7772ed6dc.
Report an issue: GitHub.