findOrphanedAppData() works, and the safety design is sound — NEVER_DELETE guarding password managers and credentials, a 30-day age threshold, and a check against installed bundle IDs. The problem is reach: it cannot see the directories where apps actually put their bulk.
Evidence
A real case from this machine. Cursor had been uninstalled — no Cursor.app in /Applications, nothing from mdfind — yet 5.6 GB of its data remained, none of which the orphan scan could surface:
| Path | Size | Why it was missed |
|---|---|---|
~/Library/Application Support/Cursor | 4.1G | directory not in ORPHAN_DIRS, and Cursor is not a reverse-DNS name |
~/.cursor | 1.0G | dotfile in $HOME, outside every scanned root |
~/.local/share/cursor-agent | 355M | XDG data path, outside every scanned root |
~/Library/Application Support/Caches/cursor-updater | 144M | nested under Application Support, not scanned |
Inside the 4.1 GB: 3.7 GB of User/globalStorage (a 1.7 GB agent worker directory, a 1.0 GB state.vscdb and a 1.0 GB .backup of it) plus 384 MB of User/History.
Two independent causes
1. ORPHAN_DIRS omits Application Support (packages/clean/src/orphans.ts):
const ORPHAN_DIRS = [
{ basePath: macPaths.caches, ... },
{ basePath: macPaths.logs, ... },
{ basePath: macPaths.savedState, ... },
{ basePath: macPaths.webkit, ... },
{ basePath: macPaths.httpStorages, ... },
]Caches and logs are the small leftovers. Application Support is where an uninstalled app leaves gigabytes — it is the highest-value root for this feature and the one not covered.
2. The name pattern only matches reverse-DNS:
const BUNDLE_ID_PATTERN = /^(com|org|net|io)\.[a-z]/iPlenty of apps name their directory after the app, not the bundle ID — Cursor, Code, Slack, Postman, Notion. Every one of those is invisible today even inside the roots that are scanned.
Proposed
- Add
~/Library/Application SupporttoORPHAN_DIRS, and~/Library/Containersand~/Library/Group Containersalongside it. - Match two name shapes: the existing reverse-DNS pattern, and a plain app-name form resolved against installed app names (from
readAppInfoPlist'sCFBundleName/CFBundleDisplayName) rather than bundle IDs alone. - Add an opt-in set of non-Library roots for dotfile and XDG leftovers —
~/.<appname>,~/.local/share/<appname>,~/.config/<appname>. These need care:~/.ssh,~/.gnupg,~/.awsand similar must be excluded, and the existingNEVER_DELETElist is the right mechanism, extended with path-based rules.
Safety notes
Application Support holds real user data, not just caches, so this needs more care than the cache/log roots do:
- Keep the 30-day age threshold, and consider raising it for this root specifically.
- Never auto-select. These should be reported for explicit confirmation, with size and last-modified shown per item.
- Flag the ones that may hold recoverable user content.
conversation-search.dbin Cursor'sglobalStorageheld past AI conversations — worth surfacing as "may contain data you cannot read without the app" rather than deleting silently.
Related
- Project-local build caches, the other blind spot found in the same audit — tracked separately.