Install
openclaw skills install @holgergruenhagen/macos-health-checkCheck a Mac used as a work computer: memory pressure, CPU load, disk use, startup items, app leftovers, backup state. Read-only commands, cleanup only as proposals with a rollback, deletes only to the Trash.
openclaw skills install @holgergruenhagen/macos-health-checkMeasure first, then propose, then act only after the owner approves. Every command in the
section "Measurements" below is read-only, needs no sudo, and stays out of privacy-protected
folders. Cleanup is always a proposal with benefit, risk and rollback (section "Cleanup proposals").
Everything this skill needs is in this one file.
Works in two modes:
This skill has no os filter on purpose, so an agent on a Linux gateway can use it in paste mode.
sudo, no tmutil writes, no defaults write, no launchctl
load/unload/bootout, no kill, no mdutil -i/-E, no memory_pressure -l (it simulates
pressure instead of reading it).~/Library/Mobile Documents), ~/Library/CloudStorage, the Photos library,
Mail, Messages, Safari data, removable or network volumes, or other apps' containers
(~/Library/Containers, ~/Library/Group Containers). Reading them either fails silently,
opens a macOS privacy dialog, or downloads cloud-only files. Do not use osascript/System
Events, sfltool, or security. When a number from such a place matters, ask the owner to
read it in Finder or System Settings → General → Storage.trash <path> (/usr/bin/trash, built into current macOS) or Finder,
never rm, rm -rf, unlink, srm, or tools that empty the Trash. Check the path still
exists before and is gone after ([ -e "<path>" ]). If trash fails, stop and ask.mktemp (never a fixed, predictable name), tell the owner its path in the report, and propose moving it to the Trash
when done.trash for anything removed. Stop at the first surprise.All commands run as the logged-in user, without sudo, and avoid privacy-protected folders.
macOS has no timeout command; set time limits in the tool that runs the command. Paste mode:
hand the owner one section at a time and ask for the full output.
sw_vers
sysctl -n hw.model hw.memsize hw.ncpu machdep.cpu.brand_string
uptime
softwareupdate --history | tail -5
hw.memsize is bytes (divide by 1073741824 for GiB).uptime matters for memory and swap: long uptimes pile up compressed memory and swap.softwareupdate --list/-l repeatedly; it contacts Apple and can take minutes.
Never --install.memory_pressure | tail -1
sysctl vm.swapusage kern.memorystatus_vm_pressure_level
vm_stat | head -12
top -l 1 -o mem -n 15 -stats pid,command,mem,cmprs
memory_pressure without options only reads. Never add -l (simulates pressure).kern.memorystatus_vm_pressure_level: 1 normal (green), 2 warning (yellow), 4 critical (red).vm_stat counts pages; page size is in its first line (16384 bytes on Apple silicon).top output by app: browsers spread over many helper processes.sysctl -n vm.loadavg
top -l 2 -s 3 -o cpu -n 10 -stats pid,command,cpu,time | tail -12
pmset -g therm
top sample has no CPU percentages; the second (after -s 3 seconds) does. tail
keeps only the second.hw.ncpu) for minutes means the Mac is busy, not only one
spike.pmset -g therm shows thermal or CPU speed limits if any are active.df -h / /System/Volumes/Data
diskutil apfs list | grep -E "Capacity (In Use|Not Allocated)|Name:" | head -20
tmutil listlocalsnapshots /
Largest visible folders in the home folder, skipping protected places (hidden folders are not measured; they hold settings and keys):
cd ~ && for d in *; do
case "$d" in
Desktop|Documents|Downloads|Library|Pictures|Movies|Music|Public) continue;;
esac
[ -d "$d" ] && [ ! -L "$d" ] && du -xsk "$d" 2>/dev/null
done | sort -n | tail -15
Size of the three ~/Library folders that usually grow without holding private data:
cd ~/Library && du -xsk Caches Logs Developer 2>/dev/null | sort -n
Everything else in ~/Library (app support data, browser data, containers, mail) is not measured;
the owner reads it under System Settings → General → Storage.
Drill down only into a folder found above, one level at a time:
du -xsk "$HOME/Library/Caches"/* 2>/dev/null | sort -n | tail -15
du -k prints KiB; divide by 1048576 for GiB.df on APFS shows the shared container; "free" includes purgeable space macOS can reclaim.ls -1 ~/Library/LaunchAgents /Library/LaunchAgents /Library/LaunchDaemons 2>/dev/null
launchctl list | awk 'NR==1 || $3 !~ /^com\.apple\./' | head -60
systemextensionsctl list
For one launch agent or daemon, read what it starts and who signed it:
plutil -p ~/Library/LaunchAgents/<label>.plist
codesign -dv --verbose=2 "<path of Program or ProgramArguments[0]>" 2>&1 | grep -E "Identifier|TeamIdentifier|Authority=Developer"
launchctl list columns: PID (- = not running), last exit status, label./bin/sh, python3, node), codesign shows the
Apple binary; check the script path in ProgramArguments instead.sfltool dumpbtm needs an admin password; do not use it
without announcing it.Vendor prefixes of installed apps (including helper apps inside bundles), then support folders named like a bundle ID whose vendor matches no installed app:
vendor_of() { sed -E 's/^group\.//; s/^[A-Z0-9]{10}\.//' | tr 'A-Z' 'a-z' | cut -d. -f1-2; }
vendors=$( { mdfind -onlyin /Applications "kMDItemContentType == 'com.apple.application-bundle'"
ls -d ~/Applications/*.app 2>/dev/null; } | while read -r a; do
/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$a/Contents/Info.plist" 2>/dev/null
done | vendor_of | sort -u )
echo "installed vendors: $(printf '%s\n' "$vendors" | wc -l)"
for base in Preferences Caches "Application Support" "Saved Application State" LaunchAgents Logs; do
ls -1 "$HOME/Library/$base" 2>/dev/null | sed 's/\.plist$//; s/\.savedState$//' |
grep -E '^[A-Za-z0-9-]+\.[A-Za-z0-9-]+\.' | grep -iv -e '^com\.apple\.' -e '^systemgroup\.' |
while read -r n; do
v=$(echo "$n" | vendor_of)
printf '%s\n' "$vendors" | grep -qxF "$v" || echo "$base/$n"
done
done | sort | head -150
group. or Team ID. Country domains such as jp.co.<vendor> collapse to two parts;
check those by hand.com.amplitude, org.chromium, org.webkit, com.github),
command-line tools and package managers, the agent runtime itself, background helpers from
M5, and the owner's own scripts. Check vendor and Team ID before proposing anything.du -sk <exact path> only after the owner has picked it for removal; skip anything inside Containers or
Group Containers (privacy dialog). Ask the owner to check those in Finder./Library (system-wide) need an admin password to move; list them, size them,
and give the owner one exact command to run in their own Terminal.tmutil destinationinfo
tmutil status
destinationinfo lists Time Machine targets and whether one is mounted; status shows a
running backup (Running = 1).tmutil listlocalsnapshots / (M4) shows the newest local snapshot; Time Machine makes
one with each backup, so its timestamp is a good hint for the last run.tmutil latestbackup needs Full Disk Access for the calling process and fails without it. Do
not grant that; ask the owner to read the last backup time in System Settings → General → Time
Machine.mdutil -s /
top -l 2 -s 3 -o cpu -n 20 -stats pid,command,cpu | grep -E "mds|mdworker|backupd|photoanalysisd|bird|cloudd" | tail -10
mdutil -s only reads the indexing status. Rebuilding the index (-E) needs approval.bird/cloudd busy for a long time point to iCloud sync, backupd to Time Machine.Nothing in this file runs without the owner's explicit approval of the specific action.
One numbered row per action, biggest measured effect first:
| # | Action | Benefit (measured) | Risk | Rollback | Dialog / admin |
|---|---|---|---|---|---|
| 1 | Move ~/Library/Caches/<vendor> to the Trash | 2.9 GiB, du -sk 14:02 | App rebuilds the cache, first start slower | Put back from Trash | none |
| 2 | Quit and remove login item <app helper> | ~400 MB RAM, top 14:05 | Feature of <app> stops starting at login | Re-enable in Login Items | System Settings, owner clicks |
Rules for the list:
/Library) and how the
owner can check them in Finder.| Finding | Safe action | Avoid |
|---|---|---|
| Large user cache of one app | trash ~/Library/Caches/<folder> with the app quit | Emptying all of ~/Library/Caches, cleaner apps that empty the Trash |
| Leftovers of an uninstalled app | trash each exact path from the checked candidate list | Wildcards, vendor names without Team ID check |
Leftovers in /Library | Give the owner one exact command with exact paths for their own Terminal; they type the admin password | Running sudo yourself |
Leftovers inside ~/Library/Containers | Owner opens the folder in Finder (open ~/Library/Containers) and drags exact folders to the Trash | Sizing or listing inside containers from the agent's shell |
| Orphaned launch agent | Quit the program, trash the plist, log out and in, check launchctl list | launchctl bootout/unload without approval |
| System or network extension | Report it with developer and state; the owner decides, with their IT or security vendor if managed | Removing, disabling, or giving removal steps for any extension |
| High memory from a browser | Show per-app totals, suggest closing tabs or enabling the browser's memory saver | Killing processes |
| Swap high after long uptime | Suggest a restart at a convenient time, then measure again | Purge tools |
| Disk almost full | List the five biggest measured folders; suggest moving archives to external or network storage (copy, verify count and size, then Trash the original) | Deleting local Time Machine snapshots, moving files the owner has not named |
| Spotlight busy for hours | Show mdutil -s / and the busy processes; suggest waiting or excluding a build folder in System Settings → Spotlight | mdutil -E without approval |
[ -e "<path>" ] && echo present.trash "<path>" (one path per argument, no wildcards).[ -e "<path>" ] || echo gone.YYYY-MM-DD HH:MM <action>
command: <exact command>
before: <value, command>
after: <value, command>
rollback: <how>