Skip to content

fix(maintenance): confirm before deleting Time Machine snapshots, add --dry-run/--yes - #85

Open
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1787003271-timemachine-safety
Open

fix(maintenance): confirm before deleting Time Machine snapshots, add --dry-run/--yes#85
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1787003271-timemachine-safety

Conversation

@devin-ai-integration

Copy link
Copy Markdown

What does this PR do?

Follow-up to #55. maintenance --timemachine deleted every local snapshot the moment it was invoked — no confirmation, no dry run, no tests — while clean (a less consequential operation) has confirm + -y + -d. Local snapshots are the user's only rollback point while the backup disk is disconnected, so this brings it in line:

$ mac-cleaner-cli maintenance --timemachine

Found 12 Time Machine local snapshot(s).
Deleting them is irreversible. macOS recreates snapshots automatically once a backup disk is connected.
? Delete 12 local snapshots? (y/N)
  • --yes skips the prompt, --dry-run reports [DRY RUN] Would delete N Time Machine snapshots and spawns nothing.
  • Declining exits quietly instead of printing "No maintenance tasks specified".
  • Snapshots are listed once in the command layer (new listTimeMachineSnapshotDates) and the dates are passed into clearTimeMachineSnapshots({ dates, dryRun }), so the prompt isn't drawn over by the ora spinner and tmutil is only invoked once for listing. clearTimeMachineSnapshots() with no arguments still lists on its own, and re-validates any caller-supplied date against the regex before it reaches tmutil.

Three other bugs in the same module:

  1. Unparseable tmutil output was reported as success. Any line not matching ^\d{4}-\d{2}-\d{2}-\d{6}$ was dropped, so a format/locale change would produce success: true, "No Time Machine local snapshots found". Now "output present but nothing recognized" returns a distinct Could not parse tmutil output (N unrecognized line(s)), while genuinely empty output still means no snapshots.
  2. Wrong sudo capability probe. sudo -n true fails for a sudoers entry scoped NOPASSWD: /usr/bin/tmutil — the recommended setup — so the tool wrongly claimed it could not delete. It now probes sudo -n /usr/bin/tmutil listlocalsnapshotdates.
  3. Timeouts reported as Process exited with code null. On the spawn timeout path Node kills the child and close fires with code === null; that branch now reports Timed out after 60000ms (killed by SIGTERM).

Plus docs: README's maintenance section only listed --dns/--purgeable; --timemachine is now documented with the sudo requirement and the irreversibility caveat.

Type of change

  • Bug fix
  • Improvement to existing feature
  • Documentation

Related issue

Follow-up to #55.

Checklist

  • bun run lint passes
  • bun run test passes (361 tests, +17: src/maintenance/timemachine.test.ts is new — date parsing, dry run, invalid dates, sudo-required, partial failure, timeout — plus the --timemachine branches of maintenanceCommand, previously uncovered)
  • bun run build succeeds
  • New tests added for new behavior
  • I've tested this on macOS locally — no Mac available; verified on Linux that maintenance --help shows the new flags and that a missing tmutil surfaces as Failed to list Time Machine snapshots: tmutil not available.... The real-Mac paths (confirm → delete, --dry-run count, sudo probe) need one manual run on macOS.

Notes for reviewer

Not addressed here, to keep the diff focused: execCommand now exists in three near-identical copies (dns-cache.ts, purgeable.ts, timemachine.ts) and only this copy has the timeout fix. Worth extracting into a shared helper, but dns-cache.ts uses child_process.exec rather than spawn and its tests mock exec, so that consolidation belongs in its own PR.

Link to Devin session: https://app.devin.ai/sessions/9d4d3a6c910b43ecb8efd09fe9129ae5
Requested by: @guhcostan

… --dry-run/--yes

maintenance --timemachine deleted every local snapshot immediately, with no confirmation and no dry run, unlike every other destructive path in the CLI. Snapshots are also listed once up front so tmutil output that cannot be parsed is reported instead of being silently reported as 'no snapshots found', and the sudo capability probe now runs tmutil itself so a tmutil-scoped NOPASSWD sudoers entry is detected.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@guhcostan guhcostan self-assigned this Aug 17, 2026
@devin-ai-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant