Maintaining macOS Health

SkillMonitoring & ops

Hands-on playbook for macOS disk cleanup, dev-machine optimization, and proactive health alerting. Use when the Mac is full or slow, when a process persistently burns CPU, when a kernel panic / watchdog timeout / vm-compressor-space-shortage / Jetsam event happened, when the user asks to free disk space, audit storage, set up disk/memory/CPU alerts, or restore the same monitoring on a new Mac. Built around Mole (`mo` CLI) for safety guards plus a custom LaunchAgent-based alerter for active warnings. Covers Apple Silicon laptops with heavy AI/Docker workloads. Not for general macOS support, hardware diagnostics, networking issues, GUI / window-manager bugs, Time Machine recovery, or broken app installs.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Maintaining macOS Health skill

What this skill tells your AI

The instructions your AI receives, as published by codealive-ai/ai-driven-development in skills/maintaining-macos-health/SKILL.md and read by ahel’s review.

Recovery and prevention playbook for macOS disk and memory crises. Validated against a real watchdog-timeout kernel panic on Apple Silicon caused by vm_compressor segments saturated to 100 % with the disk over 90 % full. The same playbook works for routine cleanup or first-time setup on a new machine.

Table of contents

  • When to use
  • Skill layout
  • Core mental model
  • Standard workflows
    • First interactive use: optional disk responses
    • A. "Free space NOW" (incident response)
    • B. "Set up alerting" (new machine or first time)
    • C. Alerter stopped working / making noise
    • D. "Uninstall an app cleanly"
  • Safety rules (non-negotiable)
  • Domain quirks captured
  • Outcomes scale

When to use

Trigger on any of:

  • Disk free < 20 % or user complains about being out of space
  • Watchdog-timeout / kernel panic / "no checkins from watchdogd"
  • New JetsamEvent-*.ips with vm-compressor-space-shortage
  • "Mac is slow", swap > 6 GB, sustained Critical memory pressure
  • A process persistently consumes a core, or the whole machine stays CPU-saturated
  • User wants to set up monitoring/alerting from scratch
  • Migration to a new Mac → restore the same alerter
  • General "clean my Mac" / "audit storage" / "free space" requests

Skill layout

FileUse for
references/triage.mdFirst 5 minutes — which signal fired, which tier of cleanup to start with
references/cleanup-tiers.mdTiered cleanup playbook (10 tiers, zero-risk → discuss-first), copy-paste-safe shell blocks
references/never-touch.mdCategories that must not be deleted even under sudo (Mole-derived blacklist + incident-derived additions)
references/mole-techniques.mdWhat Mole does that we borrow: marker→target map for mo purge, safe-path validators, age thresholds
references/alerting.mdFull alerter design: disk/memory/Jetsam critical triggers plus sustained CPU anomaly detection, incident lifecycle, hysteresis, notifier choices, install/restore commands
references/optional-disk-responses.mdFirst-use offer, installation and operation of two independent opt-ins: emergency Mole cache cleanup below 2%, and a Codex cleanup plan/page at or below 5% with exact-session continuation.
assets/mac-health-disk, assets/disk_*.pyConsent-gated disk controller, deterministic inventory, restricted Codex runner and authenticated local selection/confirmation page. Both modes default off.
assets/mole-exact-file.sh, assets/mole-core-1.39.0.jsonCompatibility-pinned exact-file adapter to Mole; no general mo clean, sudo, or shell commands from the agent.
assets/mac-health-checkProduction-ready bash script (~250 lines, bash 3.2 compatible)
assets/mac-health-actionBackground action dispatcher for read-only Codex/Claude investigations and explicitly confirmed graceful process stopping
assets/com.local.mac-health-check.plistLaunchAgent plist with StartCalendarInterval (StartInterval is broken on laptops)
assets/config.shDefault config with safe thresholds
assets/render-cleanup-plan.pyInteractive HTML cleanup-plan UI. Renders categorised checkboxes from a JSON of scan findings, serves on 127.0.0.1:18347, opens browser, waits for the user's selection, writes it to /tmp/cleanup-selection-<ts>.json. Used by Workflow A.
assets/apply-cleanup-selection.pyThe only sanctioned way to apply a cleanup selection. Reads selected_items from a selection JSON and executes each item's command field. Enforces protected-override check + path validation + Mole-compatible operations log. Prevents drift between what the user picked and what gets deleted. Supports --dry-run.

Read the relevant reference before acting. Do NOT operate from memory of these files — the details are calibrated to a real incident and small changes break safety.

Core mental model

  1. Monitor passively — Stats menubar (brew install --cask stats) — you see issues forming, not just when they explode.
  2. Alert actively, diagnose quietly — disk, memory, Jetsam, and whole-system CPU saturation are critical. A single process burning CPU is a silent advisory only after a long sustained window; routine samples stay in the log.
  3. Cleanup tiers — start zero-risk (caches, orphan data), only escalate to project artifacts and sudo categories if needed. Mole's mo purge and mo clean are the right primary tools.
  4. Mole is the safety floor — even when running shell commands by hand, follow Mole's path-validation rules: never delete inside /System, /bin, /usr, /etc, /var/db outside specific allowlisted subpaths; bin/ only under .NET; vendor/ only under PHP; protect AI/password/VPN/keychain bundle IDs.

Standard workflows

First interactive use: optional disk responses

On the first operational, interactive use on a Mac (including an existing installation upgraded to this skill), read references/optional-disk-responses.md and offer both options separately in the user's language. Do this before routine setup; during an incident, do not delay immediate triage. A request to edit or review this skill is not consent to activate it on the development machine. Background invocations must never prompt for enrollment.

  • Emergency cleanup, free space < 2%: permission for a bounded Mole cleanup of explicitly approved regenerable caches without another question at incident time. Explain the exact profile, irreversible deletion, possible cache rebuild/download cost, and exclusions before accepting consent. This is not permission for general mo clean, project purge, Trash emptying, sudo, or process termination.
  • Agent cleanup plan, free space <= 5%: permission to launch the chosen agent automatically, inspect non-secret storage metadata, and open the local selection page in the default browser. Explain provider usage/cost and what metadata leaves the Mac. The agent may propose deletion but may not perform it; Submit is a selection, and applying still requires a separate confirmation.

Record each explicit answer independently; silence, a generic "set up monitoring", or approval of one option does not authorize the other. Keep declined choices across sessions and upgrades. Ask again only on user request, a new machine, or a material change to the consent scope. Existing monitoring continues without either option.

Implementation scope: the bundled controller supports a pinned Mole 1.39.0 adapter and a restricted Codex CLI session with the same cleanup-plan UI used by Workflow A. Emergency cleanup covers only old Homebrew package downloads and npm content-cache files. Automated planning runs Workflow A's fixed read-only du, Mole clean/purge preview, Docker inventory and Downloads audit. The automatic executor remains narrower: it can delete only controller-verified regenerable cache files; storage-map and possible user-data findings are visible but disabled and require a later interactive Workflow A session. Read the reference, disclose these limits and verify local compatibility before recording approval with mac-health-disk configure. Installation alone never enables a mode. No consent is inferred from a feature-development request.

A. "Free space NOW" (incident response)

  1. Triage — read references/triage.md, identify which signal fired and how urgent.
  2. Snapshot baselinedf -h /System/Volumes/Data and write down free GB.
  3. Run all scans, don't delete yetdu audit of $HOME subdirs, mo clean --dry-run, mo purge --dry-run --debug, docker system df -v, ~/Downloads audit. Capture everything; deletion comes only after user picks via the UI.
  4. Resolve unknown items before building JSON — for every candidate > 500 MB whose purpose you cannot explain in one sentence (unfamiliar app, unfamiliar bundle ID, unfamiliar dotfolder, vendor-specific cache, ML model weights, VM image, etc.), research it first: check references/never-touch.md for a known entry, then delegate a quick lookup to the web-searcher subagent ("what is <path or bundle id> on macOS, is it safe to delete in 2026"). Wait for the answer, then write a concrete description (1-3 sentences in the user's language) into the item — what it is, who created it, what feature uses it, what breaks if deleted, whether it auto-recreates. Never show the report with vague placeholders like "unknown" or "ML data" — that defeats the point of the UI. If a web lookup contradicts never-touch.md, prefer the web answer (it's fresher) and propose an update to the reference file.
  5. Build the data JSON — every candidate becomes a structured item (id, label, path, size_bytes, age_days, kind, command, mandatory description, optional protected + warning). Write to /tmp/cleanup-data-<ts>.json. Use schema from assets/render-cleanup-plan.py docstring.
  6. Render and open the cleanup UI:
    python3 .../assets/render-cleanup-plan.py /tmp/cleanup-data-<ts>.json
    
    The script starts a one-shot HTTP server on 127.0.0.1:18347, opens the page in the user's default browser, and blocks until the user clicks Submit or Cancel. On submit it writes /tmp/cleanup-selection-<ts>.json and prints that path to stdout. Tell the user out loud: "браузер открыт — поставь галочки, нажми Submit, потом пингани меня". Then stop and wait.
  7. After the user pings — read the selection JSON, render the user's choices back in chat (categories, item list, total GB, any protected overrides flagged ⚠), and ask one explicit confirmation before deleting. Don't run anything until they say "go".
  8. Apply via the helper script — never hand-rolled rm:
    python3 .../assets/apply-cleanup-selection.py /tmp/cleanup-selection-<ts>.json
    
    The script reads selected_items from the selection JSON and executes each item's command field, with built-in safeguards: protected items must appear in protected_overrides or are skipped; commands are validated against a hard-protected path list and a ..-component check before execution; every action is logged to ~/.config/mole/operations.log in Mole-compatible TSV. --dry-run previews without executing. Do not write your own rm blocks in the apply phase — that's how you delete items the user explicitly unchecked. The selection JSON is the single source of truth; if it's not in selected_items, it does not get deleted. Run df -h /System/Volumes/Data before and after for the user-visible delta.
  9. Stop at goal — most users target 100 GB free. Don't go below that just for sport.

The Python script is bash-3.2-friendly, uses only stdlib, and is safe to run from inside the agent's shell. Hard-protected items (per references/never-touch.md) must always appear in the UI with "protected": true + a concrete warning string — the UI dims them and requires a per-item confirm dialog before they can be checked. Never omit a protected item that user data depends on (Telegram tdata, Bear database, password-manager containers, etc.) — visibility teaches the user the surrounding risk.

B. "Set up alerting" (new machine or first time)

Complete the first-use offer above; preserve existing choices when restoring monitoring. For the optional disk responses, also follow references/optional-disk-responses.md to install the bundled helper and dependencies without enabling either mode. Record activation only after independent explicit consent.

  1. Copy assets/mac-health-check and assets/mac-health-action to ~/bin/ (mkdir first; chmod +x).
  2. Copy assets/com.local.mac-health-check.plist to ~/Library/LaunchAgents/.
  3. Copy assets/config.sh to ~/.config/mac-health/config.sh (mkdir first).
  4. brew install vjeantet/tap/alerter (NOT terminal-notifier — it's broken in 2026 on Sequoia/Tahoe).
  5. brew install --cask stats for passive layer.
  6. launchctl load -w ~/Library/LaunchAgents/com.local.mac-health-check.plist.
  7. First run permission prompt: open alerter once interactively (alerter --message test) so macOS asks for Notification Center permission.
  8. Tell the user: 7-day calibration is silent (logs only). Edit config.sh after a week if pattern noisy.

The calibration window applies to the original disk/memory/Jetsam critical sensors. CPU uses conservative developer-workstation defaults and starts immediately; process advisories are silent and deduplicated for the full incident lifetime.

Verify with launchctl list | grep mac-health (should show PID and exit 0) and tail -f ~/Library/Logs/mac-health/health.log.

C. "Already have alerter, but it stopped working / making noise"

Read references/alerting.md § Troubleshooting. Common causes:

  • Stuck/old terminal-notifier (the cask) instead of alerter — replace.
  • LaunchAgent not loading after macOS update — launchctl bootstrap gui/$(id -u) <plist>.
  • Notifications going to Script Editor — TCC permission was revoked, re-grant.
  • Constant alerts during heavy dev work — touch ~/.config/mac-health/silent to suppress.

D. "Uninstall an app cleanly"

mo uninstall <app> — Mole scans 12+ locations for app traces (Application Support, Containers, Group Containers, Caches, Preferences, Saved State, LaunchAgents, LaunchDaemons, login items, etc.). Always show dry-run first, never bypass.

Safety rules (non-negotiable)

  1. Never delete without dry-run + user confirmation for any tier ≥ 5 or any sudo operation.
  2. Never bypass references/never-touch.md — even if user explicitly asks. Push back, explain the consequence.
  3. mo purge and mo clean always with --dry-run first. Show estimated reclaim, get confirm.
  4. For Time Machine backups: tmutil delete <path>, never rm. TM-tagged paths require the tmutil API.
  5. For sudo cleanup of /Library, /private/var/db/*: only the allowlisted subpaths from references/never-touch.md § Sudo allowlist.
  6. No unrestricted auto-cleanup hooks tied to alerts. The only exception is the independently approved, bounded emergency Mole cache profile below 2% in references/optional-disk-responses.md. Its exact-file dry-run is mandatory; prior profile consent replaces the incident-time confirmation only for that profile. Planning at <= 5% never grants deletion permission. Emergency consent cannot authorize ordinary cleanup tiers or protected paths.
  7. No auto-kill hooks tied to CPU alerts. A CPU advisory may offer Stop Process…, but only as an explicit user action. Revalidate PID/executable identity and ownership, confirm, send SIGTERM first, and require a separate confirmation before SIGKILL.
  8. Don't delete swap files. rm /private/var/vm/swapfile* while running = guaranteed kernel panic.
  9. Apply phase reads only the selection JSON. Never hand-roll rm blocks or hard-code paths from the earlier scan when applying. Real incident: agent applied the default-selected recordings list from the original scan, ignoring that the user had unchecked them in the UI before submitting. The fix is structural — use assets/apply-cleanup-selection.py which iterates selected_items from the selection JSON only.

Automated planning uses the helper's typed format_version: 2 branch, with a separate confirmation on the local page bound to that exact selection. The emergency executor is the sole separate profile-consent path described in rule 6; it cannot reuse or broaden a user's interactive selection.

Domain quirks captured

  • macOS Tahoe (26.x) ships /bin/bash 3.2.57. set -u + local var (no init) = unbound on first reference. The shipped script handles this.
  • LaunchAgent does not inherit user PATH. Plist must declare EnvironmentVariables.PATH and use absolute paths for interpreters.
  • StartInterval clock pauses during sleep on Apple Silicon laptops (radar 6630231). Use StartCalendarInterval with explicit minute entries (the shipped plist has all 12).
  • terminal-notifier is effectively unmaintained (last release 2019-11) and silently fails on Sequoia/Tahoe Apple Silicon. Use alerter instead.
  • osascript display notification from launchd attributes to "Script Editor" and is unreliable. Use alerter from launchd context.
  • log show --last 6m is too slow (30+ s) for periodic checks. Poll /Library/Logs/DiagnosticReports/JetsamEvent-*.ips instead — async write delay is acceptable on a 5-min cadence.
  • JetsamEvent-*.ips files live in /Library/Logs/DiagnosticReports/ (system-wide), NOT ~/Library/Logs/DiagnosticReports/.
  • macOS ps %cpu is a decaying average over up to one minute and is measured relative to one logical core, so a process may exceed 100 %. Whole-system CPU from the second iostat sample is 0–100 % across the machine. iostat is much lighter than starting top every five minutes.
  • CPU notifications resolve an owning app without reading command arguments: first from known tool paths (Playwriter, SourceCraft, Logi Options+), then from the outer .app bundle in the executable/parent chain, then from the executable fallback. Alerts show both App and Process so helpers such as Codex (Renderer) are attributed to ChatGPT.
  • CPU counters reset after a gap longer than 15 minutes, so sleep and missed calendar firings cannot masquerade as consecutive high-CPU readings. Open incidents remain open but need fresh recovery readings before rearming.
  • APFS purgeable space lags behind actual deletion by minutes. After cleanup, df may not show the change immediately; wait or run diskutil info /System/Volumes/Data | grep "Container Free".
  • Claude Desktop vm_bundles/claudevm.bundle/ is Claude Cowork, not "Claude Code sandbox" — it's a ~10 GB Ubuntu VM image (rootfs.img, sessiondata.img, efivars.fd, vmIP) for Anthropic's sandboxed code-execution feature. It is auto-provisioned at every Claude Desktop launch via an SHA1 integrity check, so its recent mtime ≠ user activity. Technically safe to delete (no chat/MCP impact), but Claude Desktop silently re-downloads ~10 GB on next launch and runs at ~55 % CPU while doing so. The Claude Code CLI does NOT use this bundle. Recommended classification: Tier 10 discuss-first with quit-Claude-Desktop pre-step and a warning that the bundle returns until Anthropic ships an opt-out toggle (open in anthropics/claude-code#57371).
  • General rule: if you encounter a folder/bundle you can't describe in one sentence (especially > 500 MB), don't guess — delegate a quick lookup to the web-searcher subagent before writing the item's description. See Workflow A step 4.

Outcomes scale

A representative recovery from a Mac that hit ~8 % free after long memory-pressure sessions on a heavily-loaded dev profile (Docker, multiple AI tools, IDEs, browsers):

  • ~25 % of total disk capacity recovered in a 4-hour session
  • Largest single contribution: project build artifacts via mo purge (~30–50 GB across many scan paths)
  • Stale IDE installations + caches + preferences: ~10 GB
  • Docker reclaim (unused images, dead builders, orphan volumes): ~10 GB
  • ~/Downloads review (old installers, recordings, archived repos): ~15 GB
  • Package-manager caches (npm, pnpm, gradle, maven, cargo, brew): ~5 GB
  • Sudo-tier cleanup (system logs, vendor-app depots): ~5–10 GB

Active alerter installed with 7-day calibration window; verified via synthetic disk-trigger test before going live. Stats menubar app installed for passive monitoring.

Numbers scale with workload and disk size. Light users will see less; heavy AI/Docker/IDE users will see more.

Signals

GitHub stars
136
Forks
10
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
maintaining-macos-health
Source
github.com/codealive-ai/ai-driven-development