https://github.com/silvansky/xcode-clr
Delete stale Xcode DerivedData and worktree build/ directories.
https://github.com/silvansky/xcode-clr
derived-data macos xcode
Last synced: 2 days ago
JSON representation
Delete stale Xcode DerivedData and worktree build/ directories.
- Host: GitHub
- URL: https://github.com/silvansky/xcode-clr
- Owner: silvansky
- Created: 2026-05-13T08:40:22.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-06-10T06:41:33.000Z (14 days ago)
- Last Synced: 2026-06-10T08:17:57.848Z (14 days ago)
- Topics: derived-data, macos, xcode
- Language: Python
- Homepage:
- Size: 21.5 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# xcode-clr
Delete stale Xcode DerivedData, worktree `build/` directories, and long-unused iOS Simulators.
Single-file Python 3 CLI. No dependencies. macOS only.
## What it scans
- `~/Library/Developer/Xcode/DerivedData/*` — each project folder. Skips shared caches (`ModuleCache.noindex`, `SDKStatCaches.noindex`, `CompilationCache.noindex`).
- `build/` inside every git worktree of every project Xcode has built (auto-derived from DerivedData `WorkspacePath`). Add extras with `--worktree-root`, disable auto with `--no-auto`.
- iOS Simulator devices (`xcrun simctl list devices`) not booted in a long time. Disable with `--no-simulators`.
## Staleness rules
An item is marked `to_be_removed` if **any** apply:
- DerivedData's `WorkspacePath` (from `info.plist`) no longer exists → `source missing`.
- `max(LastAccessedDate, folder mtime)` is older than 7 days → `stale >7d` (override with `--days`).
- Worktree `build/` folder mtime older than 7 days → `stale >7d`.
- Simulator last booted (`lastBootedAt`) older than 14 days → `stale >14d` (override with `--simulator-days`).
- Simulator whose runtime is no longer installed → `runtime unavailable`.
Simulators that are **currently booted** or were **never booted** (Xcode's default templates) are never removed.
## Usage
```
xcode-clr [--dry-run] [--json] [--yes] [--days N] [--worktree-root PATH]
```
| Flag | Effect |
|---|---|
| *(none)* | Print table, prompt once, delete on `y`. |
| `--dry-run` | Print table, never delete. |
| `--json` | Emit JSON to stdout (all scanned items, `to_be_removed` flag). No prompts, no delete. |
| `--yes` / `-y` | Skip confirmation; delete everything marked. |
| `--days N` | Staleness threshold for DerivedData & worktree builds (default `7`). |
| `--simulator-days N` | Staleness threshold for simulators (default `14`). |
| `--worktree-root PATH` | Extra git worktree root to scan. Repeatable. |
| `--no-auto` | Disable auto-discovery of worktree roots from DerivedData. |
| `--no-simulators` | Skip scanning iOS Simulator devices. |
| `--all` | List every item found (stale and fresh). Deletion still targets only stale ones. |
## Install
Clone anywhere, make the script executable, then symlink it into any directory on your `PATH`:
```sh
git clone https://github.com/silvansky/xcode-clr.git
chmod +x xcode-clr/xcode-clr
# pick whichever PATH dir you use:
ln -s "$PWD/xcode-clr/xcode-clr" /opt/homebrew/bin/xcode-clr # Apple Silicon Homebrew
ln -s "$PWD/xcode-clr/xcode-clr" /usr/local/bin/xcode-clr # Intel Homebrew
ln -s "$PWD/xcode-clr/xcode-clr" "$HOME/.local/bin/xcode-clr" # user-local
```
Verify with `which xcode-clr && xcode-clr --dry-run`.
The shebang is `/usr/bin/python3` (system Python 3.9+, stdlib only — no `pip install` needed).
## Claude skill
The repo ships a [Claude Code](https://docs.claude.com/en/docs/claude-code) skill at `.claude/skills/xcode-clr/SKILL.md`. When loaded, Claude will reach for `xcode-clr` automatically on prompts like "free up disk space", "clean DerivedData", "Xcode is eating my drive". It defaults to `xcode-clr --json` for safe, structured inventory and only invokes destructive commands after you confirm.
Install once, globally:
```sh
ln -s "$PWD/xcode-clr/.claude/skills/xcode-clr" "$HOME/.claude/skills/xcode-clr"
```
(or run `claude` from inside this repo — project-local `.claude/skills` is picked up automatically).
Uninstall: `rm "$HOME/.claude/skills/xcode-clr"`.
## Configuration
Optional config at `~/.config/xcode-clr/config.json` (or `$XDG_CONFIG_HOME/xcode-clr/config.json`):
```json
{
"worktree_roots": ["~/code/my-ios-app", "~/work/other-repo"],
"threshold_days": 14,
"auto_discover": true,
"scan_simulators": true,
"simulator_threshold_days": 14
}
```
Paths support `~` expansion.
Environment override: `XCODE_CLR_WORKTREE_ROOTS=/path/a:/path/b` (colon-separated).
Precedence: CLI flags > env > config file > built-in defaults. `worktree_roots` from all sources are merged & deduped.
## JSON output
```json
{
"scanned_at": "2026-05-13T08:13:09Z",
"threshold_days": 7,
"items": [
{
"path": "/Users/.../DerivedData/Foo-abc",
"kind": "derived_data",
"source_path": "/Users/.../Foo.xcworkspace",
"size_bytes": 1234567,
"last_accessed": "2026-04-13T06:55:31Z",
"mtime": "2026-04-13T06:55:31Z",
"age_days": 30.05,
"reason": "stale >7d",
"to_be_removed": true
}
]
}
```
`kind` is `"derived_data"`, `"worktree_build"`, or `"simulator"`. `reason` is `null` for kept items. Simulator items add `name`, `udid`, `state`, and `available`; their `last_accessed` is the device's last boot time.
## Examples
```sh
xcode-clr --dry-run # preview
xcode-clr --json | jq '.items[] | select(.to_be_removed)'
xcode-clr --yes # non-interactive cleanup
xcode-clr --days 30 # only purge things older than 30d
xcode-clr --all --dry-run # full inventory, stale and fresh
xcode-clr --worktree-root ~/work/repo-a --worktree-root ~/work/repo-b
xcode-clr --no-auto # only scan explicit/config roots
xcode-clr --no-simulators # skip iOS Simulator devices
xcode-clr --simulator-days 30 # only purge sims unbooted >30d
xcode-clr --json | jq '.items[] | select(.kind=="simulator" and .to_be_removed)'
```