Skip to content

xcode-disk-cleanup

  • Treat every cleanup request as permission to audit, not permission to delete.
  • Never mutate storage before presenting exact candidate IDs, paths or simulator identifiers, measured sizes, risks, and regeneration costs.
  • Treat a category-level request made after an audit (for example, “remove all outdated documentation caches”) as approval of the matching, current frozen selection set when it was already fully enumerated to the user. Execute in that turn after revalidation; do not restate the set and ask again. No special command or repeated confirmation is required. Broad requests made before an audit, such as “clean Xcode” or “delete everything,” remain audit-only.
  • Default ordinary files and directories to Trash. Explain that space is not reclaimed until Trash is emptied, and request separate approval before doing so.
  • Revalidate identity and running-process usage immediately before mutation.
  • Never use wildcards, unbounded rm -rf, sudo, SIP changes, or manual deletion inside system-managed CoreSimulator and MobileAsset directories.
  • Preserve archives and dSYMs unless the user proves the exact distributed build is backed up elsewhere and separately approves deletion.
  • Never delete Package.resolved, signing assets, credentials, custom DocSets, the active Xcode, a running simulator, or an Xcode that uniquely provides a required SDK/toolchain.
  • Report summed candidate sizes separately from actual APFS space recovered.

Read references/safety-model.md before applying cleanup.

  1. Confirm the host is macOS and the bundled script is available.
  2. Check for active Xcode, Simulator, xcodebuild, Swift build, test, archive, and package-resolution processes.
  3. Treat an open Xcode or Simulator application as context, not an automatic cleanup blocker. Defer an affected cleanup when a build, test, archive, or package-resolution process is active, an exact candidate is actively needed by that operation, or a supported deletion API requires the app/device to be stopped.
  4. Do not ask the user to quit apps merely because they appear in ps. For regenerable documentation caches, moving an old cache while Xcode is open may temporarily remove local documentation until it reloads, but does not require quitting Xcode or Simulator. State that cost instead.
  5. Determine optional source roots for project-local .build and .derived-data scanning. Do not crawl the whole home directory.
Terminal window
python3 "${SKILL_DIR}/scripts/xcode_disk_cleanup.py" audit \
--output-dir .xcode-disk-cleanup-audit \
[--scan-root /path/to/projects]

Read both generated files:

  • .xcode-disk-cleanup-audit/report.md
  • .xcode-disk-cleanup-audit/audit.json

The script audits DerivedData, documentation caches, CocoaPods caches, simulator devices and runtimes (including stale superseded beta runtimes), orphan runtime volumes, simulator dyld caches, Xcode-managed components, Xcode installers, installed Xcodes, archives (including never-distributed orphans), DeviceSupport for every platform, diagnostic logs, XCTest device clones, legacy DocSets, and explicitly requested project roots.

Shared compiler caches (ModuleCache.noindex, precompiled SDK modules) and the global SwiftPM download cache are deliberately out of scope: they are always in use on an active development machine, and clearing them trades real build-time pain for little lasting space. Do not propose them.

Use the category router:

Finding Reference
DerivedData, module caches, project .build, CocoaPods references/generated-data.md
Simulator devices, runtimes, dyld caches, XCTest clones references/simulators.md
Archives, dSYMs, DeviceSupport, logs, DocSets references/release-artifacts.md
Xcode DMGs/XIPs and installed Xcodes references/xcode-installations.md
Confirmation and deletion behavior references/safety-model.md

Do not mechanically accept a scanner classification. Verify uncertain ownership, custom paths, backups, and toolchain requirements.

Sort by recoverable GiB and show:

  1. Candidate ID
  2. Category and exact path/identifier
  3. Recoverable GiB
  4. Risk: regenerable, destructive, or preserve
  5. Evidence — a short “why this is stale” phrase (age in days, superseded-by, missing workspace), not just a classification label
  6. What must be rebuilt, redownloaded, or permanently lost
  7. Proposed action

Keep the proposal scannable:

  • Render folder candidates as clickable file:// links so the user can inspect them before approving. Show the candidate ID only where the user needs it to approve, not as the item’s display name.
  • Carry the audit’s evidence and reason fields into your notes. The script already explains why each item is stale (“Unchanged for 4 days”, “Superseded by 16.4.1 (325) archived 2026-07-21”, “the same documentation stays available”); dropping that context turns an explainable proposal into a bare file list.
  • Hide individual items below roughly 0.5 GiB; the full itemized list stays in report.md for reference. Do not pad the proposal with a “small items” bucket — it adds questions, not signal.
  • Group unavailable simulators into a table per runtime.
  • Give every category its own subheading with its total size, even small ones like documentation caches or orphan archives. A category summarized in a trailing paragraph under another category’s table gets overlooked, and overlooked items cannot be meaningfully approved.
  • Trust the scanner’s classification unless you have live contrary evidence. In particular, Orphan archive candidates already encode proof (no Organizer distribution record plus a newer archive of the same app); do not demote them back to preserve just because archives are preserve-by-default elsewhere.

Format each category like this example, adapting labels to the machine:

### Orphaned DerivedData — 15.5 GiB · regenerable
All four belong to workspaces that no longer exist.
| Folder | Size | Why stale |
|---|---:|---|
| [RocketSim-beqqrx…](file:///Users/me/Library/Developer/Xcode/DerivedData/RocketSim-beqqrximmvoaakbpvjshhqhuacyv) | 5.6 GiB | Workspace deleted; unchanged for 1 day |
| [RocketSim-fjapct…](file:///Users/me/Library/Developer/Xcode/DerivedData/RocketSim-fjapctdhvtzcaaabmtsyumkmcarf) | 4.0 GiB | Only package downloads, no build products; unchanged for 4 days |

Include:

  • Total candidate GiB by risk
  • Current free disk space
  • A warning that APFS cloning and snapshots make candidate sizes non-additive
  • A direct request for approval of exact IDs. When the user selects a category, restate every selected ID, the summed size, risk, and proposed action in a single frozen selection set. State once that a later plain-language removal request approves the matching set. Do not ask the user to repeat IDs, use a prescribed phrase, or reconfirm after an unchanged revalidation.

5. Expand category selections and apply only approved IDs

Section titled “5. Expand category selections and apply only approved IDs”

After a completed audit, a request such as “delete all stale DerivedData” or “clean up all outdated documentation caches” may approve every currently audited candidate in that category only when all of the following hold:

  1. Every selected item has already passed the category-specific validation and has an exact stable ID, path or simulator identifier, size, risk, and action.
  2. The assistant prints the complete expanded ID list, summed size, and action as a frozen selection set and explains that a plain-language category request will approve it.
  3. The user then makes an unambiguous, affirmative request to remove that category. This is the single approval and authorizes only the IDs in the displayed set, not the category in general. Apply it immediately after revalidation; never require a magic phrase or another user turn.
  4. Revalidation succeeds. A refreshed audit does not invalidate approval when every approved candidate retains the same ID, identity, risk, and action. Silently use the refreshed audit and proceed. Never add a new candidate.
  5. If an approved item changes, becomes active, or is reclassified, exclude and report that item. Continue with unchanged approved items when safe; ask again only if proceeding would materially change the approved scope or action.

Never infer category membership from a broad request before the audit, and never include preserve items, archives/dSYMs, active assets, or simulator operations unless their separate safeguards and approvals are met.

Invoke apply only after direct per-ID approval or a plain-language approval of a frozen expanded ID set from the current audit:

Terminal window
python3 "${SKILL_DIR}/scripts/xcode_disk_cleanup.py" apply \
--audit-file .xcode-disk-cleanup-audit/audit.json \
--ids "<approved-id-1>" "<approved-id-2>" \
--confirm I_APPROVED_THE_SELECTED_ITEMS \
--output .xcode-disk-cleanup-audit/cleanup-result.json

Simulator device and runtime deletions are irreversible and require a second approval plus:

--confirm-irreversible I_APPROVED_IRREVERSIBLE_SIMULATOR_DELETION

A stale runtime is only proposed when simctl reports it deletable, no installed Xcode SDK resolves to its build, and a newer runtime supersedes it on the same platform. This is the pattern behind leftover beta platforms in Xcode Settings → Components after installing a newer Xcode beta.

Do not pass either confirmation phrase unless the corresponding user approval is present in the conversation.

“Separate approval” means distinct, explicit intent for the irreversible Simulator category, not necessarily a separate message. After exact Simulator IDs and permanent data loss have been shown, a single request such as “remove the DerivedData and unavailable simulators” contains both ordinary approval and separate category-specific irreversible approval. Revalidate and execute both in that turn. A generic “clean everything” does not provide irreversible approval.

  1. Re-run the audit.
  2. Compare df free space before and after.
  3. Distinguish “moved to Trash” from immediately reclaimed space.
  4. Confirm protected and unapproved candidates remain.
  5. Report successes, failures, and deferred items.
  • Search .xip, .dmg, and .zip installers through Spotlight and common download folders.
  • Suggest an installer only when its parsed version matches an installed Xcode; otherwise report it as uncertain.
  • An older Xcode may only be suggested, never automatically selected, when it is not active/running and a newer same-channel installation exists.
  • Spotlight kMDItemLastUsedDate reflects LaunchServices opens, not command-line use. Combine it with xcode-select, DEVELOPER_DIR, running processes, .xcode-version, CI scripts, and unique SDK/toolchain evidence.
  • Never infer that a stable Xcode supersedes a beta, or vice versa.
  • Audit completed without mutation
  • Every item has an exact size and stable ID
  • Candidate and actual recovered space are separate
  • Archives/dSYMs are preserve-by-default
  • Active processes and selected Xcode are protected
  • User directly approved exact IDs or made an unambiguous category request after its fully enumerated frozen selection set was shown
  • Irreversible operations received separate approval
  • Post-cleanup audit confirms only approved items changed