getsentry / getsentry/XcodeBuildMCP
[Bug]: Orphaned workspaces are never swept — test-products for deleted project paths persist forever (126 GB past max age)
- Lingua principale
- TypeScript
- Stelle
- 6.4k
- Fork
- 319
- Metriche di merge delle PR
- Nessuna PR unita negli ultimi 30g
Descrizione
## Bug Description
`~/Library/Developer/XcodeBuildMCP/workspaces` reached **143 GB** on my machine — 285 `.xctestproducts` bundles across 391 workspace directories, with 141 GB of that under `*/test-products/`. It was the single largest consumer on a ~1 TB disk that had run down to its last 96 GB.
This is adjacent to #524, but I believe it is a **distinct root cause**, and the retention knobs proposed there would not reclaim any of this space.
**A workspace is keyed by its project path, and it is never garbage-collected when that path stops existing.** `pruneManagedTestProductsDirectory` only sweeps while a server is active for that workspace (with a 1 h cooldown). In agent-driven workflows that create and delete a git worktree per task, the worktree path is gone permanently once the task finishes — so no server ever starts for that workspace again, and neither `TEST_PRODUCTS_MAX_AGE_MS` nor `TEST_PRODUCTS_MAX_COUNT` is ever evaluated against its bundles. They become effectively immortal.
### Evidence that the sweep never runs for these workspaces
| Bundle age | Bundles | Size |
| --- | ---: | ---: |
| > 3 days (`TEST_PRODUCTS_MAX_AGE_MS`) | 256 | 126.4 GB |
| > 7 days | 252 | 124.4 GB |
| > 14 days | 242 | 119.4 GB |
| > 30 days | 5 | 2.4 GB |
256 bundles are past the 3-day max age and still on disk. Had the age-based sweep run for those workspaces, all of them would have been removed.
Count-based GC cannot catch this either: the **largest number of bundles in any single workspace is 16**, far below `TEST_PRODUCTS_MAX_COUNT = 100`. Every workspace is individually "under limit" while the aggregate sits at 141 GB.
### The orphan ratio
A single project accounts for **223 of the 391 workspace directories**. That project currently has **17 git worktrees**. The remaining ~206 workspaces are keyed to worktree paths deleted weeks ago that will never be built again.
Because each workspace is separately under both thresholds, total consumption is unbounded in *the number of project paths ever built*, rather than in builds per path. Heavy agent/worktree use makes that number grow monotonically and silently — this accumulated over roughly three months without ever surfacing anywhere a user would normally look, since `~/Library` is hidden in Finder and macOS Storage settings folds it into an undifferentiated "Developer" bucket.
## Steps to Reproduce
1. Use an MCP client workflow that creates a fresh git worktree per task (`git worktree add`), which is the default for several coding-agent harnesses.
2. Have the agent call `build_sim` / `build_run_sim` / `test_sim` inside that worktree.
3. Complete the task and `git worktree remove` the worktree.
4. Repeat across a few hundred tasks over several months.
5. `du -sh ~/Library/Developer/XcodeBuildMCP/workspaces`
A quick way to see the orphaned share on an affected machine:
```bash
WS=~/Library/Developer/XcodeBuildMCP/workspaces
# bundles past the 3-day max age that are still present
find "$WS" -mindepth 3 -maxdepth 3 -name '*.xctestproducts' -type d \
! -newermt "$(date -v-3d +%Y-%m-%d)" -print0 | xargs -0 du -sch | tail -1
# most bundles retained in any single workspace (compare against MAX_COUNT = 100)
find "$WS" -mindepth 3 -maxdepth 3 -name '*.xctestproducts' -type d \
| awk -F/ '{print $(NF-2)}' | sort | uniq -c | sort -rn | head -1
```
## Expected Behavior
Test-product bundles belonging to a workspace whose project path no longer exists should be reclaimed. Two options that would each fix this:
- A **global sweep at server start** that drops workspace directories whose keyed path is missing from disk, independent of whether a server is ever active for that workspace again.
- A **total-size budget across all workspaces** (e.g. `XCODEBUILDMCP_TEST_PRODUCTS_MAX_TOTAL_BYTES`), rather than per-workspace age/count limits that each pass individually while the aggregate grows without bound.
## Actual Behavior
Every workspace ever created persists indefinitely along with its bundles. 126 GB across 256 bundles sits well past the 3-day max age, completely untouched, because the only code path that would delete it requires an active server for a directory that no longer exists.
## Notes
- Each workspace does carry `state/filesystem-lifecycle/last-cleanup`, so the lifecycle bookkeeping is already in place — it simply never runs again once a workspace is orphaned.
- A global sweep would also help users who do not use worktrees at all but who rename, move, or delete projects over time.
- Roughly 90% of the bytes are byte-identical across bundles (unchanged third-party dependency output recopied verbatim on every build), so content-addressed storage or hardlinking would be a further large win, though it is secondary to the missing GC.
- Related: #524 (retention limits too permissive in practice), #525 (DerivedData duplication between Xcode.app and the MCP).
## Debug Output
```
XcodeBuildMCP Doctor
Server Version: 2.7.0
## Checks
- xcode: ok Xcode 26.6 - Build version 17F113 (/Applications/Xcode.app/Contents/Developer)
- process-tree: ok Running under Xcode: No; 5 process entries
- axe: ok Available: Yes; UI automation: Yes; Video capture: Yes
- xcodemake: ok Enabled: No; Binary: No; Makefile: Not checked
- mise: warning Running under mise: No; Available: No
- debugger-dap: ok Selected backend: lldb-cli; lldb-dap available: Yes
- manifest-tools: ok Total tools: 82; Workflows: 15
- runtime-registration: ok Enabled workflows: debugging, doctor, project-discovery,
session-management, simulator-management, simulator,
ui-automation, utilities, xcode-ide; Registered tools: 58
- xcode-ide-bridge: ok Workflow enabled: Yes; Connected: No; Proxied tools: 0
- sentry: ok Enabled: Yes
```
## Editor/Client
Claude Code (desktop app)
## MCP Server Version
2.7.0
## LLM
Claude Opus 5
Guida per i contributori
Nessuna guida per i contributori indicizzata per questo repository
Direzione di ricerca
Start by locating pruneManagedTestProductsDirectory, the workspace lifecycle cleanup, and the server-start path; review how TEST_PRODUCTS_MAX_AGE_MS and TEST_PRODUCTS_MAX_COUNT are applied. Reproduce the orphaned-workspace case with deleted project paths, then verify that stale workspace data is reclaimed or bounded across workspaces without requiring a server for each path.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Valutazione
- Stack tecnologico
- typescript
- Ambito
- devtools
- Tipo di issue
- Bug
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Stato di attività
- Attiva
- Chiarezza
- Abbastanza chiara
- Idoneità per principianti
- 48/100