Time Machine kept telling me my backups were failing. The drive mounted fine and Settings showed nothing obviously wrong. Reconnecting it and restarting didn't help. The GUI says that something failed, not why. The answer was on the command line.
Start With What Time Machine Thinks It Has
tmutil destinationinfo
This was the first real clue. There were two destinations with the same name. Only one was mounted. The other was a leftover from before I erased and re-added the drive; macOS had kept the old entry and added a new one beside it.
The full configuration lives in a plist, with more detail than any settings pane shows:
defaults read /Library/Preferences/com.apple.TimeMachine
Each destination has a DestinationID, the volume UUIDs it expects, a list of SnapshotDates, and a RESULT code for its last attempt. The stale entry's last backup was months old and its result was non-zero. The current drive showed RESULT = 0.
Check What Is Actually on the Disk
APFS Time Machine backups are snapshots on the destination volume:
diskutil list external
diskutil apfs listSnapshots <volume>
Each backup appears as com.apple.TimeMachine.. Mine showed regular backups up to a point, then a multi-day gap. That gave me a window to search the logs.
Read the Unified Log Properly
One gotcha first: in zsh, log is a shell builtin. Call the binary directly:
/usr/bin/log show --last 3d --info --style compact \
--predicate 'subsystem == "com.apple.TimeMachine"'
Most of the output is noise. In my case these lines were harmless:
fs_snapshot_list failed: Operation not supported, logged when Time Machine probes volumes that are not APFS.connection invalid, routine XPC chatter.Expected SnapshotInProgressContainer metadata type but found APFSBackup, logged even on healthy backups.
The useful lines were a leftover .previous folder (a backup that never finished) and Skipping scheduled backup: No destinations are potentially available for the days the drive was disconnected.
Find Which Destination the Alert Is About
This was the decisive check. The notification itself carries an ID:
/usr/bin/log show --last 7d --style compact \
--predicate 'process == "TMHelperAgent" OR process == "usernoted"' | grep TimeMachine
The alert was com.apple.TimeMachine.DrySpell., the "hasn't backed up in N days" warning. The ID belonged to the stale destination. Time Machine rotates between configured destinations, so it kept trying to reach a disk that no longer existed and warning about it. Both entries had the same name, so every warning looked like it was about the drive in front of me.
The Second Cause: An Unclean Eject
The kernel log explained the gap in snapshots:
/usr/bin/log show --last 7d --style compact \
--predicate 'process == "kernel" AND eventMessage CONTAINS "<disk>"'
When the drive mounted again, the kernel logged a reload after an unclean unmount. I had disconnected the drive while a backup was still finishing. That backup was lost and left behind as a .previous folder, and the drive then sat disconnected for days.
The Fix
Remove the stale destination. This removes only the setting; nothing on any disk is deleted:
sudo tmutil removedestination <stale-DestinationID>
Then leave the drive connected and let a full backup finish. To verify:
tmutil destinationinfo # exactly one destination
tmutil status # Running = 0 once it finishes
diskutil apfs listSnapshots <volume> | tail -3 # a fresh .backup snapshot
Is Ejecting Really That Important?
For a USB stick, rarely. For a Time Machine drive, more so. APFS is designed to survive power loss, so a single unclean unplug is usually harmless. Doing it habitually has two costs. You lose whatever backup was in progress; and occasionally the backup store is corrupted, at which point Time Machine has to create a new backup, a full re-copy that discards your history.
Ejecting manually is tedious enough that I stopped doing it, so I scripted it. unplug stops any running backup, ejects every external physical disk, and posts a notification saying whether it is safe:
#!/bin/zsh
notify() { osascript -e "display notification \"$1\" with title \"unplug\"" 2>/dev/null; }
tm_running() { tmutil status 2>/dev/null | grep -q 'Running = 1'; }
if tm_running; then
tmutil stopbackup
for i in {1..30}; do tm_running || break; sleep 1; done
tm_running && { notify "Time Machine didn't stop. Don't unplug yet."; exit 1; }
fi
failed=()
for d in ${(f)"$(diskutil list external physical | awk '/^\/dev\/disk/{print $1}')"}; do
diskutil eject "$d" >/dev/null 2>&1 || failed+=$d
done
if (( ${#failed} )); then
notify "Couldn't eject ${failed[*]}. Don't unplug yet."; exit 1
fi
notify "All external disks ejected. Safe to unplug."
Put it in a directory on your PATH, or wire it into a Shortcuts "Run Shell Script" action with a keyboard shortcut. Then ejecting takes one keystroke.
Conclusion
The alert was real, but it pointed at a disk that no longer existed, with a name identical to the one that did. None of that is visible in System Settings. tmutil destinationinfo, the Time Machine plist, APFS snapshot listings, and a well-filtered log show turned a vague "backup failed" into two specific, fixable causes. When a macOS feature only gives you a red badge, the explanation is usually one predicate away in the unified log.