maintaining-macos-health

$npx mdskill add CodeAlive-AI/ai-driven-development/maintaining-macos-health

Automates macOS disk and memory cleanup for optimized performance

  • Identifies full disks, high CPU usage, kernel panics, and other health issues.
  • Deploys Mole CLI tool to safely manage disk space and optimizes system resources.
  • Uses custom LaunchAgent-based alerting to notify users of potential problems proactively.
  • Provides a comprehensive playbook for setting up alerts and restoring monitoring on new machines.

SKILL.md

.github/skills/maintaining-macos-healthView on GitHub ↗
---
name: maintaining-macos-health
version: 1.2.0
description: 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.
---

# Maintaining macOS Health

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](#when-to-use)
- [Skill layout](#skill-layout)
- [Core mental model](#core-mental-model)
- [Standard workflows](#standard-workflows)
  - [A. "Free space NOW" (incident response)](#a-free-space-now-incident-response)
  - [B. "Set up alerting" (new machine or first time)](#b-set-up-alerting-new-machine-or-first-time)
  - [C. Alerter stopped working / making noise](#c-already-have-alerter-but-it-stopped-working--making-noise)
  - [D. "Uninstall an app cleanly"](#d-uninstall-an-app-cleanly)
- [Safety rules (non-negotiable)](#safety-rules-non-negotiable)
- [Domain quirks captured](#domain-quirks-captured)
- [Outcomes scale](#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

| File | Use for |
|---|---|
| `references/triage.md` | First 5 minutes — which signal fired, which tier of cleanup to start with |
| `references/cleanup-tiers.md` | Tiered cleanup playbook (10 tiers, zero-risk → discuss-first), copy-paste-safe shell blocks |
| `references/never-touch.md` | Categories that **must not** be deleted even under sudo (Mole-derived blacklist + incident-derived additions) |
| `references/mole-techniques.md` | What Mole does that we borrow: marker→target map for `mo purge`, safe-path validators, age thresholds |
| `references/alerting.md` | Full alerter design: disk/memory/Jetsam critical triggers plus sustained CPU anomaly detection, incident lifecycle, hysteresis, notifier choices, install/restore commands |
| `assets/mac-health-check` | Production-ready bash script (~250 lines, bash 3.2 compatible) |
| `assets/mac-health-action` | Background action dispatcher for read-only Codex/Claude investigations and explicitly confirmed graceful process stopping |
| `assets/com.local.mac-health-check.plist` | LaunchAgent plist with `StartCalendarInterval` (StartInterval is broken on laptops) |
| `assets/config.sh` | Default config with safe thresholds |
| `assets/render-cleanup-plan.py` | Interactive 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.py` | The **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

### A. "Free space NOW" (incident response)

1. **Triage** — read `references/triage.md`, identify which signal fired and how urgent.
2. **Snapshot baseline** — `df -h /System/Volumes/Data` and write down free GB.
3. **Run all scans, don't delete yet** — `du` 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**:
   ```bash
   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`**:
   ```bash
   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)

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 auto-cleanup hooks tied to alerts.** Alerts notify; user decides. Documented anti-pattern (Google SRE, also confirmed by community 2025-2026 — see `references/alerting.md`).
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.

## 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](https://github.com/anthropics/claude-code/issues/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.

More from CodeAlive-AI/ai-driven-development

SkillDescription
agentic-readinessAudit and improve repositories for reliable agentic work across Codex and Codex App, Claude Code, and OpenCode. Use when reviewing AGENTS.md or CLAUDE.md quality and discovery, instruction routing in monorepos or meta-repos, agent settings, MCP configuration, skills, subagents, context budgets, or repository organization for coding agents.
agents-consiliumQuery external AI agents (Codex, Gemini, OpenCode, Claude Code headless) in parallel for independent second opinions, code review, bug investigation, and consensus on high-stakes decisions. Agents and models are configurable in config.json. Use for architecture choices, security review, or ambiguous problems where independent perspectives matter. Not for simple questions answerable from docs or the codebase — use web search or repo exploration instead.
bug-fix-protocol8-step disciplined bug-fix protocol that treats every production bug as two failures — the code defect itself and the testing system that allowed it through. Use when fixing a production bug, investigating a regression, writing a post-mortem, or auditing a missed defect. Triggers on "fix this bug", "production bug", "regression test", "post-mortem", "test gap", "why did the tests miss this".
code-that-fits-in-your-headSoftware engineering heuristics from Mark Seemann's book Code That Fits in Your Head (2021). Use when writing new code, reviewing code, refactoring, designing APIs, handling validation and invariants, writing unit tests, debugging defects, performing security review (STRIDE), or setting up a new code base. Covers decomposition (cyclomatic complexity, 80/24 rule, cohesion, fractal architecture), encapsulation (invariants, parse-don't-validate, Postel's law), outside-in TDD (walking skeleton, AAA, triangulation, devil's advocate), API design (affordance, poka-yoke, CQS), git/PR hygiene (50/72 commits, small commits, code review), feature flags, Strangler pattern, bisection debugging, logging with decorators, and STRIDE threat modelling. Not for language-specific syntax, framework tutorials, production incident response, or performance profiling.
fetch-url-as-markdownFetch a web page (URL) and return clean Markdown via local trafilatura, with Exa MCP as a fallback for JS-rendered or anti-bot pages. Use when the user asks to read, fetch, scrape, summarize, or quote a URL — prefer this over the built-in WebFetch tool. Don't use for binary files (PDFs, images, archives) or for fetching API/JSON endpoints.
fpf-problem-solvingFirst Principles Framework (FPF) — thinking amplifier. Use when user wants to think through a complex problem, architect a system, evaluate alternatives, decompose complexity, classify problems, define quality attributes, plan rigorously, apply an FPF pattern to a first useful result, decide under uncertainty, establish causality, reason about time and trends, describe or synthesize architecture, check mathematical model fit, distinguish relation kinds or occurrences, govern ontic/U-kind admission, publish multi-view artifacts, refresh SoTA packs, trace provenance, or improve pattern quality. Also triggers on: FPF, bounded contexts, SoTA packs, assurance calculus, decision theory, causal reasoning, temporal reasoning, architecture description, modularity, constraint-governed unfolding, narrative rendering, structural adequacy, cultural evolution, quality gates, lexical discipline, FPF Parts A-I. Not for simple task planning, general philosophy, or Agile unrelated to FPF.
hooks-managementManage hooks and automation for coding agents (Claude Code, Codex CLI, OpenCode). Use when users want to add, list, remove, update, or validate hooks. Triggers on requests like "add a hook", "create a hook that...", "list my hooks", "remove the hook", "validate hooks", or any mention of automating agent behavior with shell commands or plugins.
investigating-repository-historyInvestigate GitHub repository history before risky code changes using git blame/log, GitHub PRs, review comments, squash/rebase/cherry-pick/rename heuristics, and cited evidence. Use when asking why code exists, whether a change is safe, what PR introduced behavior, or before editing API, compatibility, security, concurrency, persistence, migration, or performance-sensitive code.
maintaining-windows-healthHands-on playbook for Windows 11 disk cleanup, dev-machine optimization, and proactive health alerting. Use when the PC is full or slow, when a BSOD / Kernel-Power 41 / crash dump / commit-memory pressure happened, when the user asks to free disk space, audit storage, set up disk/memory alerts, or restore the same monitoring on a new PC. Built around native Microsoft-supported tooling (Storage Sense, cleanmgr, DISM, pnputil, vssadmin, wevtutil, powercfg) as the safety floor, a drift-protected HTML cleanup UI, and a Task Scheduler + BurntToast alerter. Covers dev machines with heavy AI/Docker/WSL workloads. Not for general Windows support, hardware diagnostics, GPU/driver troubleshooting, antivirus/malware removal, Windows Update repair, networking, or app-specific performance problems unrelated to disk or memory pressure.
mcp-managementSearch, install, configure, update, and remove MCP servers across coding agents (Claude Code, Cursor, VS Code, Claude Desktop, Gemini CLI, Codex, Goose, Zed, and more). Supports multi-agent installation via npx add-mcp, the official MCP registry, and direct config editing.