benbjohnson/litestream · error
sync before disable: %w
Error message
sync before disable: %w
What it means
While disabling write support, SetWriteEnabledWithTimeout(false) tried to flush remaining dirty pages to the replica (syncToRemoteWithLock) and that sync failed. The disable is aborted — writeEnabled stays true — because silently losing dirty pages would lose committed SQLite data. The wrapped error is the underlying sync failure (conflict check, LTX upload, etc.).
Source
Thrown at vfs.go:1874
// Check timeout if specified
if timeout > 0 && time.Now().After(deadline) {
close(waitDone)
f.disabling = false
f.cond.Broadcast() // Wake any waiting Lock() calls
f.mu.Unlock()
return fmt.Errorf("timeout waiting for transaction to complete (waited %v)", timeout)
}
f.cond.Wait() // Unlocks mu, waits for signal, relocks mu
}
close(waitDone) // Stop the watcher goroutine
// Sync dirty pages if any exist
if len(f.dirty) > 0 {
if err := f.syncToRemoteWithLock(); err != nil {
f.disabling = false
f.cond.Broadcast() // Wake any waiting Lock() calls
f.mu.Unlock()
return fmt.Errorf("sync before disable: %w", err)
}
}
// Stop sync loop and ticker if running
if f.syncStop != nil {
close(f.syncStop)
f.syncStop = nil
}
if f.syncTicker != nil {
f.syncTicker.Stop()
f.syncTicker = nil
}
f.writeEnabled = false
f.disabling = false
f.cond.Broadcast() // Wake any Lock() calls waiting for disable to complete
f.logger.Info("write support disabled")
f.mu.Unlock()View on GitHub (pinned to 4ed7a308f6)
Solutions
- Read the wrapped cause; fix connectivity/credentials to the replica storage and retry SetWriteEnabled(false).
- Resolve TXID conflicts: if another writer took over, coordinate (lease) or reset local state with litestream reset before disabling.
- Keep writes enabled (the API does this automatically) and ensure the periodic sync loop succeeds first, then disable with no dirty pages.
- Verify replica health (litestream ltx listing) to confirm the remote is writable and consistent.
- Retry after the transient outage; dirty pages were preserved.
Example fix
// before
if err := file.SetWriteEnabled(false); err != nil { os.Exit(1) } // fails on S3 outage
// after
if err := file.SetWriteEnabledWithTimeout(false, 0); err != nil {
log.Warn("write-disable failed, writes still enabled; will retry", "err", err)
} Defensive patterns
Strategy: retry
Validate before calling
// Pre-check replica reachability before disabling writes:
itr, err := client.LTXFiles(ctx, 0, 0, false)
if err != nil { return fmt.Errorf("replica unreachable: %w", err) }
itr.Close() Try / catch
if err := file.SetWriteEnabled(false); err != nil {
var syncErr error
if strings.HasPrefix(err.Error(), "sync before disable:") {
// writes still enabled; backoff and retry disable
time.Sleep(backoff); retry()
}
} Prevention
- Confirm periodic syncs are succeeding before attempting disable.
- Use credential rotation that does not expire mid-operation.
- Ensure single-writer via leasing to avoid TXID conflicts.
- Retry disable with exponential backoff during storage outages.
When it happens
Trigger: Calling SetWriteEnabled(false) when len(f.dirty) > 0 and the final sync fails: replica unreachable, network error, remote has newer transactions (conflict), or storage write error during WriteLTXFile.
Common situations: Shutting down writes during a network partition or S3 outage; another writer advanced the remote TXID causing a conflict in checkForConflict; expired cloud credentials at disable time; disk issues reading the write buffer during LTX creation.
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
- sync database: %w
- upload LTX: %w
- check remote position: %w
- sync interval must be greater than 0
- replica sync: %w
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/52ec7d03fd9c7779.
Report an issue: GitHub.