Keep the local index when remote nuke cannot list or find the destination (closes #292)
check / check (push) Canceled after 0s

`remote nuke` took a destination store it could not list, or one
without `vaultik-destination.txt` while the local index held
snapshots, as a warning: it then cleared the local snapshot records,
and for an unplugged volume's empty mount point reported the store
empty and exited zero. Nuke now fails on either before it removes
anything, without its advice to run it again, which would fail the
same way. The bulk remove it runs fails on either too; only a
local-only remove still goes on with a warning. Nuke also deletes each
snapshot's remote metadata before its local record, so a failed delete
keeps the snapshot in the local index rather than leaving it on the
store as a remote-only snapshot.

Model: opus-5-5
This commit was merged in pull request #384.
This commit is contained in:
clawbot committed 2026-10-14 06:47:38 +02:00
1 parent 2125a9d451
commit 16b03dfda5
8 files changed
+142 -37

No files matched your search

+30 -21
View File
@@ -1516,7 +1516,9 @@ func (v *Vaultik) removeSnapshotRemote(
// database from the local index, and (with --remote) every snapshot
// metadata directory in remote storage. Both sides are processed so a
// "remove --all" leaves nothing behind, even when the local DB and
// remote storage have diverged.
// remote storage have diverged. Unless LocalOnly is set, it fails
// before it removes anything when the destination store cannot be
// listed or is not present by checkDestinationMarker.
func (v *Vaultik) RemoveAllSnapshots(opts *RemoveOptions) (*RemoveResult, error) {
err := v.EnsureStorageBinding()
if err != nil {
@@ -1529,11 +1531,15 @@ func (v *Vaultik) RemoveAllSnapshots(opts *RemoveOptions) (*RemoveResult, error)
}
// remoteKeys is the set of metadata/<key>/ subdirectories on the
// destination store; failures are downgraded to a warning so a
// permission-denied or unreachable remote can't block a local-only
// remove.
// destination store. Only for a local-only remove is a failure
// downgraded to a warning, so a permission-denied or unreachable
// remote can't block it.
remoteKeys, remoteErr := v.listAllRemoteSnapshotKeys()
if remoteErr != nil {
if !opts.LocalOnly {
return nil, remoteErr
}
log.Warn("Could not list remote snapshots", "error", remoteErr)
v.UI.Warningf("Could not list remote snapshots: %v.", remoteErr)
}
@@ -1595,8 +1601,7 @@ func (v *Vaultik) localSnapshotIDs() ([]string, error) {
// listAllRemoteSnapshotKeys collects the hashed remote keys
// (subdirectories under metadata/) currently present in the
// destination store. Returns (nil, err) when the store cannot be
// listed, or is not present by checkDestinationMarker; callers must
// treat that as "no remote info available," not fatal.
// listed, or is not present by checkDestinationMarker.
func (v *Vaultik) listAllRemoteSnapshotKeys() ([]string, error) {
log.Info("Listing all remote snapshots")
@@ -1780,10 +1785,12 @@ func (v *Vaultik) deleteIncompleteSnapshot(snapshotIDStr string, id any) bool {
return true
}
// removeAllLocalSnapshots removes each known local snapshot (and, unless
// --local-only, its remote metadata), returning the number of snapshots
// it could not remove. A snapshot left in the local index keeps its
// remote metadata too, since it is not an orphan remote key.
// removeAllLocalSnapshots removes each known local snapshot's remote
// metadata, unless --local-only, and then its local record, returning
// the number of snapshots it could not remove. The local record goes
// only once the metadata is deleted: dropped first, it would leave that
// metadata on the destination store as a remote-only snapshot whose name
// is lost.
func (v *Vaultik) removeAllLocalSnapshots(
localSnaps []string, opts *RemoveOptions, result *RemoveResult,
) int {
@@ -1792,6 +1799,19 @@ func (v *Vaultik) removeAllLocalSnapshots(
for _, snapshotID := range localSnaps {
log.Info("Removing snapshot", "snapshot_id", snapshotID)
if !opts.LocalOnly {
_, err := v.deleteRemoteSnapshotByKey(
snapshot.RemoteSnapshotKey(snapshotID))
if err != nil {
log.Warn("Failed to remove snapshot metadata from remote",
"snapshot_id", snapshotID, "error", err)
failures++
continue
}
}
err := v.deleteSnapshotFromLocalDB(snapshotID)
if err != nil {
log.Error("Failed to remove from local database",
@@ -1802,17 +1822,6 @@ func (v *Vaultik) removeAllLocalSnapshots(
continue
}
if !opts.LocalOnly {
_, err = v.deleteRemoteSnapshotByKey(
snapshot.RemoteSnapshotKey(snapshotID))
if err != nil {
log.Warn("Failed to remove snapshot metadata from remote",
"snapshot_id", snapshotID, "error", err)
failures++
}
}
result.SnapshotsRemoved = append(result.SnapshotsRemoved, snapshotID)
}