The scan (internal/snapshot) counts the entries it finds in each configured path, not counting excluded ones or the path itself when it is a directory (ScanResult.EntriesFound).
Under --prune, CreateSnapshot (internal/vaultik) skips the prune when any path of a snapshot made in this run held nothing: every older snapshot and its blobs are kept, a warning names each such path with its snapshot, and the run returns ErrSourcePathEmpty; the cli prints "Backup completed, but …" and exits non-zero. Without --prune this check does nothing.
resolveSnapshotPaths fails every run, with or without --prune, on a configured path that is a symlink whose target is gone, as one into a volume that is not mounted.
The README describes both; --prune's help names the empty-path case. There is no override flag.
Worth knowing
Only the current run is looked at: after one empty run, the previous snapshot is empty too.
Under --prune, a run with unreadable files and an empty path names both and returns ErrFilesUnreadable.
Deviation: the plan says nothing changes without --prune; the symlink failure applies to every run.
Deviation: for the linter's limits, code moved into recordRegularFile (unchanged), createNamedSnapshots and skipPostBackupPrune.
Judgement call: the cli logs "Backup completed with an error" for both errors.
Judgement call: --skip-errors skips a missing path as unreadable, but not such a symlink.
Model: opus-5-5
Fixes https://git.eeqj.de/sneak/vaultik/issues/388 as planned in https://git.eeqj.de/sneak/vaultik/issues/388#issuecomment-140920.
**What changed**
- The scan (`internal/snapshot`) counts the entries it finds in each configured path, not counting excluded ones or the path itself when it is a directory (`ScanResult.EntriesFound`).
- Under `--prune`, `CreateSnapshot` (`internal/vaultik`) skips the prune when any path of a snapshot made in this run held nothing: every older snapshot and its blobs are kept, a warning names each such path with its snapshot, and the run returns `ErrSourcePathEmpty`; the cli prints "Backup completed, but …" and exits non-zero. Without `--prune` this check does nothing.
- `resolveSnapshotPaths` fails every run, with or without `--prune`, on a configured path that is a symlink whose target is gone, as one into a volume that is not mounted.
- The README describes both; `--prune`'s help names the empty-path case. There is no override flag.
**Worth knowing**
- Only the current run is looked at: after one empty run, the previous snapshot is empty too.
- Under `--prune`, a run with unreadable files and an empty path names both and returns `ErrFilesUnreadable`.
- A volume mounted below a configured path is https://git.eeqj.de/sneak/vaultik/issues/395.
**Disclosures**
- Deviation: the plan says nothing changes without `--prune`; the symlink failure applies to every run.
- Deviation: for the linter's limits, code moved into `recordRegularFile` (unchanged), `createNamedSnapshots` and `skipPostBackupPrune`.
- Judgement call: the cli logs "Backup completed with an error" for both errors.
- Judgement call: `--skip-errors` skips a missing path as unreadable, but not such a symlink.
Model: opus-5-5
internal/snapshot/scanner.go line 938: a configured path that is not a directory always counts as holding something, and that includes a symlink whose target is gone. A path configured as a symlink into a volume (say ~/Photos pointing at /mnt/nas/Photos) points at nothing while the volume is not mounted. resolveSnapshotPaths (internal/vaultik/snapshot.go line 416) then keeps the unresolved symlink, the scan records only the symlink, and --prune deletes the older snapshot that holds the files, and its blobs. That is the data loss of #388, still reachable. Acceptable: such a path counts as empty (or the run fails, as it does for a path that does not exist), with a test that backs up through a symlinked path, removes the target, and checks that --prune keeps the older snapshot and its blobs.
README.md line 331: the README says the command names the empty path on every run until the path holds something again. A run that also skipped unreadable files under --skip-errors warns only about those (internal/vaultik/snapshot.go line 157) and never names the empty path. Acceptable: that run names the empty paths too, or the README says it does not.
internal/cli/snapshot.go line 118: nothing tests the new branch that reports ErrSourcePathEmpty as "Backup completed, but …", so its removal would go unnoticed. Acceptable: an entry-level test like TestEntrySnapshotCreateSkipErrorsExitsNonZeroWhenItSkips that backs up, empties the source, runs snapshot create --prune, and checks exit status 1 and that message.
Judgement call: a configured path whose only remaining entry is an empty subdirectory, such as a volume mounted below the configured path, still prunes. I took that as outside the plan, which covers the configured path itself.
Model: opus-5-5
**Review: FAIL (needs-rework)**
1. `internal/snapshot/scanner.go` line 938: a configured path that is not a directory always counts as holding something, and that includes a symlink whose target is gone. A path configured as a symlink into a volume (say `~/Photos` pointing at `/mnt/nas/Photos`) points at nothing while the volume is not mounted. `resolveSnapshotPaths` (`internal/vaultik/snapshot.go` line 416) then keeps the unresolved symlink, the scan records only the symlink, and `--prune` deletes the older snapshot that holds the files, and its blobs. That is the data loss of https://git.eeqj.de/sneak/vaultik/issues/388, still reachable. Acceptable: such a path counts as empty (or the run fails, as it does for a path that does not exist), with a test that backs up through a symlinked path, removes the target, and checks that `--prune` keeps the older snapshot and its blobs.
2. `README.md` line 331: the README says the command names the empty path on every run until the path holds something again. A run that also skipped unreadable files under `--skip-errors` warns only about those (`internal/vaultik/snapshot.go` line 157) and never names the empty path. Acceptable: that run names the empty paths too, or the README says it does not.
3. `internal/cli/snapshot.go` line 118: nothing tests the new branch that reports `ErrSourcePathEmpty` as "Backup completed, but …", so its removal would go unnoticed. Acceptable: an entry-level test like `TestEntrySnapshotCreateSkipErrorsExitsNonZeroWhenItSkips` that backs up, empties the source, runs `snapshot create --prune`, and checks exit status 1 and that message.
Judgement call: a configured path whose only remaining entry is an empty subdirectory, such as a volume mounted below the configured path, still prunes. I took that as outside the plan, which covers the configured path itself.
Model: opus-5-5
resolveSnapshotPaths now fails the run, before anything is pruned, on a configured path that is a symlink whose target is gone; TestBackupThroughSymlinkWhoseTargetIsGoneDoesNotPrune backs up through a symlink, removes the target, runs --prune, and checks that the older snapshot and everything it stored are kept.
A run that skipped the prune for unreadable files now also names its empty paths (skipPostBackupPrune); TestBackupWithEmptySourcePathAndUnreadableFileNamesBoth covers it.
Added TestEntrySnapshotCreatePruneExitsNonZeroWhenSourceIsEmpty: exit status 1, "Backup completed, but 1 source path(s) were empty", and both snapshots kept on the destination.
Model: opus-5-5
Rework:
1. `resolveSnapshotPaths` now fails the run, before anything is pruned, on a configured path that is a symlink whose target is gone; `TestBackupThroughSymlinkWhoseTargetIsGoneDoesNotPrune` backs up through a symlink, removes the target, runs `--prune`, and checks that the older snapshot and everything it stored are kept.
2. A run that skipped the prune for unreadable files now also names its empty paths (`skipPostBackupPrune`); `TestBackupWithEmptySourcePathAndUnreadableFileNamesBoth` covers it.
3. Added `TestEntrySnapshotCreatePruneExitsNonZeroWhenSourceIsEmpty`: exit status 1, "Backup completed, but 1 source path(s) were empty", and both snapshots kept on the destination.
Model: opus-5-5
README.md lines 328–329: the new sentence "The same holds when the backup found nothing in a configured path" comes right after "A file removed during the backup does not stop the prune.", so "the same" reads as "does not stop the prune". That is the opposite of what the code does, and the clause after the colon then contradicts it. Acceptable: link the empty-path case to the unreadable-files case and not to the removed-file sentence. For example, move the removed-file sentence after it, or write "When the backup found nothing in a configured path, …, the prune does not run either: …".
Landing commit message and PR body ("Worth knowing"): "Without --prune nothing changes" is no longer true after the rework. resolveSnapshotPaths (internal/vaultik/snapshot.go) now fails every run on a configured path that is a symlink whose target is gone, with or without --prune. Such a run used to succeed and record only the link. This departs from the plan (#388 (comment): "Without --prune nothing changes"), and the disclosures do not mention it. Acceptable: the commit message and PR body say that the symlink failure applies to every run, limit "nothing changes without --prune" to the empty-path check, and the PR body lists the change as a deviation from the plan.
PR body: about 280 words, over the 250-word limit. Acceptable: 250 words or fewer.
Model: opus-5-5
**Review: FAIL (needs-rework)**
1. `README.md` lines 328–329: the new sentence "The same holds when the backup found nothing in a configured path" comes right after "A file removed during the backup does not stop the prune.", so "the same" reads as "does not stop the prune". That is the opposite of what the code does, and the clause after the colon then contradicts it. Acceptable: link the empty-path case to the unreadable-files case and not to the removed-file sentence. For example, move the removed-file sentence after it, or write "When the backup found nothing in a configured path, …, the prune does not run either: …".
2. Landing commit message and PR body ("Worth knowing"): "Without `--prune` nothing changes" is no longer true after the rework. `resolveSnapshotPaths` (`internal/vaultik/snapshot.go`) now fails every run on a configured path that is a symlink whose target is gone, with or without `--prune`. Such a run used to succeed and record only the link. This departs from the plan (https://git.eeqj.de/sneak/vaultik/issues/388#issuecomment-140920: "Without `--prune` nothing changes"), and the disclosures do not mention it. Acceptable: the commit message and PR body say that the symlink failure applies to every run, limit "nothing changes without `--prune`" to the empty-path check, and the PR body lists the change as a deviation from the plan.
3. PR body: about 280 words, over the 250-word limit. Acceptable: 250 words or fewer.
Model: opus-5-5
snapshot create --prune of a snapshot whose path was an empty
directory, as the mount point of a volume that is not mounted is,
recorded an empty snapshot and then purged the older snapshots that
held the data, with their blobs.
The scan now counts the entries it finds in each configured path.
Under --prune, a run in which a path of a snapshot it made held
nothing skips the prune, keeps every older snapshot, names each such
path with its snapshot, and exits non-zero, as
#382 does for unreadable
files. Only the current run is looked at. Without --prune this check
does nothing. A configured path that is a symlink whose target is
gone now fails every run, with or without --prune.
Model: opus-5-5
README.md: the empty-path sentence now follows the unreadable-files one ("the prune does not run either"), and the removed-file sentence moved to the end of the paragraph.
The commit message and PR body say the symlink failure applies to every run, with or without --prune, and limit "nothing changes without --prune" to the empty-path check; the PR body lists the symlink failure as a deviation from the plan.
The PR body is under 250 words.
Also brought in line with the code: the ScanResult.EntriesFound comment (an entry the walk could not look up is not counted), the ErrSourcePathEmpty comment (a run that also skipped unreadable files returns ErrFilesUnreadable), and the TODO.md entry (the symlink failure applies with or without --prune).
Model: opus-5-5
Rework:
1. `README.md`: the empty-path sentence now follows the unreadable-files one ("the prune does not run either"), and the removed-file sentence moved to the end of the paragraph.
2. The commit message and PR body say the symlink failure applies to every run, with or without `--prune`, and limit "nothing changes without `--prune`" to the empty-path check; the PR body lists the symlink failure as a deviation from the plan.
3. The PR body is under 250 words.
Also brought in line with the code: the `ScanResult.EntriesFound` comment (an entry the walk could not look up is not counted), the `ErrSourcePathEmpty` comment (a run that also skipped unreadable files returns `ErrFilesUnreadable`), and the `TODO.md` entry (the symlink failure applies with or without `--prune`).
Model: opus-5-5
Review: PASS — under --prune, no run with an empty configured path or a configured symlink whose target is gone reaches the prune, the tests catch each way of breaking that, and every sentence the change adds or changes matches the code.
Model: opus-5-5
**Review: PASS** — under `--prune`, no run with an empty configured path or a configured symlink whose target is gone reaches the prune, the tests catch each way of breaking that, and every sentence the change adds or changes matches the code.
Model: opus-5-5
clawbot
merged commit e9b17cc5fd into next2026-10-15 02:49:22 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Fixes #388 as planned in #388 (comment).
What changed
internal/snapshot) counts the entries it finds in each configured path, not counting excluded ones or the path itself when it is a directory (ScanResult.EntriesFound).--prune,CreateSnapshot(internal/vaultik) skips the prune when any path of a snapshot made in this run held nothing: every older snapshot and its blobs are kept, a warning names each such path with its snapshot, and the run returnsErrSourcePathEmpty; the cli prints "Backup completed, but …" and exits non-zero. Without--prunethis check does nothing.resolveSnapshotPathsfails every run, with or without--prune, on a configured path that is a symlink whose target is gone, as one into a volume that is not mounted.--prune's help names the empty-path case. There is no override flag.Worth knowing
--prune, a run with unreadable files and an empty path names both and returnsErrFilesUnreadable.Disclosures
--prune; the symlink failure applies to every run.recordRegularFile(unchanged),createNamedSnapshotsandskipPostBackupPrune.--skip-errorsskips a missing path as unreadable, but not such a symlink.Model: opus-5-5
Review: FAIL (needs-rework)
internal/snapshot/scanner.goline 938: a configured path that is not a directory always counts as holding something, and that includes a symlink whose target is gone. A path configured as a symlink into a volume (say~/Photospointing at/mnt/nas/Photos) points at nothing while the volume is not mounted.resolveSnapshotPaths(internal/vaultik/snapshot.goline 416) then keeps the unresolved symlink, the scan records only the symlink, and--prunedeletes the older snapshot that holds the files, and its blobs. That is the data loss of #388, still reachable. Acceptable: such a path counts as empty (or the run fails, as it does for a path that does not exist), with a test that backs up through a symlinked path, removes the target, and checks that--prunekeeps the older snapshot and its blobs.README.mdline 331: the README says the command names the empty path on every run until the path holds something again. A run that also skipped unreadable files under--skip-errorswarns only about those (internal/vaultik/snapshot.goline 157) and never names the empty path. Acceptable: that run names the empty paths too, or the README says it does not.internal/cli/snapshot.goline 118: nothing tests the new branch that reportsErrSourcePathEmptyas "Backup completed, but …", so its removal would go unnoticed. Acceptable: an entry-level test likeTestEntrySnapshotCreateSkipErrorsExitsNonZeroWhenItSkipsthat backs up, empties the source, runssnapshot create --prune, and checks exit status 1 and that message.Judgement call: a configured path whose only remaining entry is an empty subdirectory, such as a volume mounted below the configured path, still prunes. I took that as outside the plan, which covers the configured path itself.
Model: opus-5-5
24152ce6d6to6c3f199b41Rework:
resolveSnapshotPathsnow fails the run, before anything is pruned, on a configured path that is a symlink whose target is gone;TestBackupThroughSymlinkWhoseTargetIsGoneDoesNotPrunebacks up through a symlink, removes the target, runs--prune, and checks that the older snapshot and everything it stored are kept.skipPostBackupPrune);TestBackupWithEmptySourcePathAndUnreadableFileNamesBothcovers it.TestEntrySnapshotCreatePruneExitsNonZeroWhenSourceIsEmpty: exit status 1, "Backup completed, but 1 source path(s) were empty", and both snapshots kept on the destination.Model: opus-5-5
Review: FAIL (needs-rework)
README.mdlines 328–329: the new sentence "The same holds when the backup found nothing in a configured path" comes right after "A file removed during the backup does not stop the prune.", so "the same" reads as "does not stop the prune". That is the opposite of what the code does, and the clause after the colon then contradicts it. Acceptable: link the empty-path case to the unreadable-files case and not to the removed-file sentence. For example, move the removed-file sentence after it, or write "When the backup found nothing in a configured path, …, the prune does not run either: …".Landing commit message and PR body ("Worth knowing"): "Without
--prunenothing changes" is no longer true after the rework.resolveSnapshotPaths(internal/vaultik/snapshot.go) now fails every run on a configured path that is a symlink whose target is gone, with or without--prune. Such a run used to succeed and record only the link. This departs from the plan (#388 (comment): "Without--prunenothing changes"), and the disclosures do not mention it. Acceptable: the commit message and PR body say that the symlink failure applies to every run, limit "nothing changes without--prune" to the empty-path check, and the PR body lists the change as a deviation from the plan.PR body: about 280 words, over the 250-word limit. Acceptable: 250 words or fewer.
Model: opus-5-5
6c3f199b41tofd5e077fa0Rework:
README.md: the empty-path sentence now follows the unreadable-files one ("the prune does not run either"), and the removed-file sentence moved to the end of the paragraph.--prune, and limit "nothing changes without--prune" to the empty-path check; the PR body lists the symlink failure as a deviation from the plan.Also brought in line with the code: the
ScanResult.EntriesFoundcomment (an entry the walk could not look up is not counted), theErrSourcePathEmptycomment (a run that also skipped unreadable files returnsErrFilesUnreadable), and theTODO.mdentry (the symlink failure applies with or without--prune).Model: opus-5-5
Review: PASS — under
--prune, no run with an empty configured path or a configured symlink whose target is gone reaches the prune, the tests catch each way of breaking that, and every sentence the change adds or changes matches the code.Model: opus-5-5