| name | nix-darwin-gc-doctor |
| description | Diagnose and safely recover nix-darwin garbage collection on macOS. Use when /nix is unexpectedly large, nix-collect-garbage fails or stalls, old system generations or result symlinks retain store paths, macOS-protected app bundles such as Karabiner or Warp block deletion, or administrator prompts need to be consolidated without weakening sudo security. |
Nix Darwin GC Doctor
Separate retention from deletion failure before changing anything. Treat profile roots and macOS-protected dead apps as different problems. Resolve SKILL_DIR to the directory containing this SKILL.md before running bundled scripts.
Diagnose
-
Confirm the host is macOS with a multi-user Nix installation.
-
Run the diagnostic summary:
"$SKILL_DIR/scripts/diagnose.sh"
-
Interpret the result:
- A large store with few dead paths indicates live GC roots.
- Many dead paths indicate reclaimable data.
- A failed GC naming an
.app or reporting Operation not permitted indicates a macOS protection blocker.
-
Measure paths retained only by old system generations and repository result roots:
"$SKILL_DIR/scripts/measure-roots.sh"
Report NAR sizes as estimates. They are not additive disk usage because store paths share references and filesystem blocks.
Run bundled diagnostics sequentially. Concurrent Nix queries create temporary roots and distort root measurements. These commands never delete store paths, but Nix may prune its own stale temporary-root metadata while inspecting GC state.
Choose Remediation
Old generations
Show the generation dates and current generation first. Obtain explicit approval for a retention period before deleting generations. Prefer configuring the existing nix-darwin module rather than adding an unrelated mechanism:
nix.gc = {
automatic = true;
options = "--delete-older-than 14d";
};
Verify that the installed nix-darwin version supports the option before editing. Do not change the retention period silently.
Result roots
List every result symlink and its target. Delete only links the user confirms are disposable. Prefer nix build --no-link for future temporary builds.
Protected dead apps
Run a store-preserving scan and write an exact manifest:
manifest=$(mktemp /tmp/nix-protected-dead-apps.XXXXXX)
"$SKILL_DIR/scripts/protected-dead-apps.sh" scan "$manifest"
"$SKILL_DIR/scripts/protected-dead-apps.sh" verify "$manifest"
The scanner covers app bundles at a dead store root and under its standard Applications tree. If the GC error names an app in another layout, add that exact .app ancestor to the manifest and run verify; never infer it from a package name.
Show the complete manifest and obtain explicit approval. Then perform one administrator invocation:
sudo "$SKILL_DIR/scripts/protected-dead-apps.sh" \
delete "$manifest" --confirm-dead-app-deletion
The script removes only listed .app bundles inside paths that Nix still reports as dead. Let Nix remove the enclosing store paths.
Inventory all required privileged work before authentication. Use one sudo -v or one root invocation after approval. Never configure passwordless sudo.
Verify
Run garbage collection and then repeat the store-preserving checks:
nix-collect-garbage
nix-store --gc --print-dead
df -h /nix
"$SKILL_DIR/scripts/diagnose.sh"
Confirm that the current system still resolves, the Nix daemon is running, and unrelated applications remain untouched. Report deleted path count, reclaimed bytes, remaining roots, and any residual macOS metadata separately.
Safety Rules
- Never delete a live store path or an entire
/nix/store/<hash-name> directory manually.
- Never run builds, profile switches, or another GC concurrently with diagnosis or protected-app deletion.
- Never select candidates by package name alone; verify dead-root membership and exact paths.
- Never remove unconfirmed generations,
result links, Trash contents, app containers, or user data.
- Never run
sfltool resetbtm, including with --help; that form still resets the database.
- Never reset LaunchServices or Background Task Management globally. Unregister only an exact stale application path with
lsregister -u when the user requests registration cleanup.
- Do not treat unrelated names such as the Haskell
warp library or GIMP's warp tool as Warp Terminal.
- Preserve dirty repository changes and existing Nix configuration outside the requested scope.
- Stop and report the exact blocker when validation fails; do not weaken permissions or disable system protections broadly.