benbjohnson/litestream · error
cannot specify both TXID & timestamp to restore
Error message
cannot specify both TXID & timestamp to restore
What it means
CalcRestorePlan rejects a restore request that supplies both a TXID and a timestamp, because the restore target is ambiguous. Exactly one of txID or timestamp must be nonzero/zero respectively. The public API surfaces this as a plain error with no wrapped cause.
Source
Thrown at replica.go:1516
// Find snapshots at the snapshot level that are before the timestamp.
snapshots, err := FindLTXFiles(ctx, r.Client, SnapshotLevel, true, func(info *ltx.FileInfo) (bool, error) {
return info.CreatedAt.Before(timestamp), nil
})
if err != nil {
return nil, fmt.Errorf("find LTX snapshots: %w", err)
}
if len(snapshots) == 0 {
return nil, nil
}
// Return the latest snapshot before the timestamp (last in the sorted list).
return snapshots[len(snapshots)-1], nil
}
// CalcRestorePlan returns a list of storage paths to restore a snapshot at the given TXID.
func CalcRestorePlan(ctx context.Context, client ReplicaClient, txID ltx.TXID, timestamp time.Time, logger *slog.Logger) ([]*ltx.FileInfo, error) {
if txID != 0 && !timestamp.IsZero() {
return nil, fmt.Errorf("cannot specify both TXID & timestamp to restore")
}
var infos ltx.FileInfoSlice
logger = logger.With("target", txID)
// Start with latest snapshot before target TXID or timestamp.
// Pass useMetadata flag to enable accurate timestamp fetching for timestamp-based restore.
var snapshot *ltx.FileInfo
snapshotItr, err := client.LTXFiles(ctx, SnapshotLevel, 0, !timestamp.IsZero())
if err != nil {
return nil, err
}
for snapshotItr.Next() {
info := snapshotItr.Item()
logger.Debug("finding snapshot before target TXID or timestamp", "snapshot", info.MaxTXID)
if txID != 0 && info.MaxTXID > txID {
continue
}View on GitHub (pinned to 4ed7a308f6)
Solutions
- Pass only one target: set timestamp to the zero value time.Time{} when restoring by TXID.
- If using the CLI, drop either -txid or -timestamp.
- Clear a stale persisted target time (SetTargetTime/ResetTime state) before issuing a TXID restore.
Example fix
// before plan, err := CalcRestorePlan(ctx, client, txID, targetTime, logger) // after var zeroTime time.Time plan, err := CalcRestorePlan(ctx, client, txID, zeroTime, logger)
Defensive patterns
Strategy: validation
Validate before calling
// Go: enforce mutual exclusivity before calling the API
if txID != 0 && !timestamp.IsZero() {
return fmt.Errorf("pass either txID or timestamp, not both")
}
plan, err := ltx.CalcRestorePlan(ctx, client, txID, timestamp, logger) Try / catch
if err != nil {
if err.Error() == "cannot specify both TXID & timestamp to restore" {
// clear one of the targets and rebuild the plan
}
return err
} Prevention
- Use the zero value time.Time{} explicitly when restoring by TXID.
- In CLIs/scripts, build flags so -txid and -timestamp are mutually exclusive.
- Reset stale target-time state (ResetTime) before TXID-based restores.
When it happens
Trigger: Calling Restore/CalcRestorePlan (or triggering SetTargetTime/ResetTime/waitForRestorePlan paths) with both txID != 0 and timestamp set, e.g. via library API CalcRestorePlan(ctx, client, txid, ts, logger) or CLI flags combining -txid and -timestamp.
Common situations: Library users computing a plan for a known TXID while leaving a default timestamp from config; scripts building restore args that always append -timestamp; restoring to a specific transaction while a stale target time is persisted.
Related errors
- find v0.3.x snapshot: %w
- find LTX snapshot: %w
- invalid -timestamp, must specify in ISO 8601 format (e.g. 20
- database already exists, skipping
- no snapshots available
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/40ef32126b3f81ee.
Report an issue: GitHub.