feat: Workspace filesystem cleanup #391
7 issues
Medium
Build Logs path not updated to workspace-scoped layout - `src/snapshot-tests/__fixtures__/cli/device/build--error-wrong-scheme.txt:8`
The Derived Data path was updated to the new workspace-keyed location (workspaces/XcodeBuildMCP-<HASH>/DerivedData/...), but the Build Logs path on line 15 still points to the old top-level logs/ directory. Per the PR description, XcodeBuildMCP-owned logs are now moved into workspace-keyed paths under shared application support. If the rendered output now writes logs under a workspace-scoped path, this fixture would be inconsistent with actual output and CLI/MCP/JSON fixtures may diverge.
Also found at:
src/snapshot-tests/__fixtures__/mcp/simulator/test--failure.txt:36
New 'Running tests' progress lines may be volatile and should be normalized - `src/snapshot-tests/__fixtures__/cli/swift-package/test--failure.txt:8-14`
The fixture now includes seven sequential 'Running tests (N completed, M failure, ...)' progress lines that were previously absent. These progress counters depend on test execution order and timing, which is typically volatile across runs. Per the skill's guardrails, volatile values should be normalized in code rather than baked into fixtures; otherwise this snapshot will become flaky on re-runs. Confirm whether these progress lines are deterministic for this fixture's input or whether normalization in fixture-io / contracts is needed.
Daemon socket directory created in world-writable /tmp without permission verification - `src/daemon/socket-path.ts:21-23`
The daemon directory is now placed under tmpdir() (typically /tmp, world-writable with sticky bit) using a predictable path xcodebuildmcp-<12-hex-chars>. Combined with ensureSocketDir's existsSync-then-mkdirSync pattern (in this file at lines 77-82), an attacker on a multi-user system could pre-create the directory or a symlink at the predictable path before the daemon starts, bypassing the intended mode: 0o700 and exposing the Unix domain socket to other local users. The previous location under ~/.xcodebuildmcp/daemons/<key> was inside the user's home directory and not subject to this race.
isPidAlive treats EPERM as alive but ignores invalid PIDs (0, negative) - `src/utils/process-liveness.ts:1-8`
process.kill(0, 0) on POSIX sends signal 0 to every process in the caller's process group, and negative PIDs target a process group rather than a single process. Because isPidAlive does not validate the input, callers passing a stale/zero/negative PID (e.g. from a corrupted registry file) can inadvertently probe or signal unrelated processes, and the function will report 'alive' based on group membership rather than the intended PID. In a cleanup path that uses liveness to decide whether to delete another workspace's artifacts, this can both prevent legitimate cleanup and, if the signal value were ever changed, target unintended processes.
Low
New JSON fixture entry has empty test name and uses description as suite - `src/snapshot-tests/__fixtures__/json/simulator/test--failure.json:370-375`
The added entry uses 'Decimal point at start creates 0' as the suite and an empty string for test. If this reflects a real change in structured output (e.g., Swift Testing free-function tests), the fixture update is intentional and aligned. If the normalization layer should split or relabel such entries, this fixture may be encoding volatile/ad-hoc data rather than a normalized envelope. Verify that the JSON envelope shape here is produced by the normalizer and matches MCP/CLI fixtures for the same run.
compactWorkspaceKey collapses workspace keys to 12-hex chars enabling collisions across workspaces - `src/daemon/socket-path.ts:16-19`
compactWorkspaceKey extracts only the trailing 12-hex-character hash from the workspace key (or hashes the whole key to 12 hex chars) for the daemon directory name, while registryPathForWorkspaceKey and logPathForWorkspaceKey continue using the full workspace key (name + hash). Two workspaces whose name slugs differ but whose path hashes collide in 12 hex chars (~2^48 space) — or where a malicious workspace name is constructed to mimic the -<12hex>compactWorkspaceKeyextracts only the trailing 12-hex-character hash from the workspace key (or hashes the whole key to 12 hex chars) for the daemon directory name, whileregistryPathForWorkspaceKeyandlogPathForWorkspaceKey` continue using the full workspace key (name + hash). Two workspaces whose name slugs differ but whose path hashes collide in 12 hex chars (~2^48 space) — or where a malicious workspace name is constructed to mimic the suffix pattern of another — would share the same daemon directory and socket while having distinct registry/log directories, leading to socket cross-talk while state remains separate.
Scheduled sweep uses stale `now` captured at scheduling time - `src/utils/workspace-filesystem-lifecycle.ts:436-437`
In scheduleWorkspaceFilesystemLifecycleSweep, resolveOptions(options) is called synchronously and the resulting resolved (with resolved.now fixed at scheduling time) is captured in the setTimeout closure and passed to runWorkspaceFilesystemLifecycleSweep. When the timer fires (after at least WORKSPACE_FILESYSTEM_LIFECYCLE_SCHEDULE_DELAY_MS, possibly much longer under event-loop pressure), the sweep uses this stale now for cooldown checks (shouldSkipForCooldown), minVisibleMs protection in isProtectedLogFile, age expiration (options.now - file.mtimeMs > options.maxAgeMs), and the marker mtime written by touchCleanupMarker. This can cause the marker to be backdated and protection windows to use outdated time, producing slightly incorrect retention decisions and cooldown evaluations.
14 skills analyzed
| Skill | Findings | Duration | Cost |
|---|---|---|---|
| xcodebuildmcp-docs-release-review | 0 | 32.5s | $0.38 |
| xcodebuildmcp-docs-command-review | 0 | 6.7s | $0.05 |
| xcodebuildmcp-runtime-boundary-review | 0 | 3m 26s | $0.19 |
| xcodebuildmcp-snapshot-fixture-review | 3 | 5m 23s | $2.71 |
| xcodebuildmcp-structured-output-review | 0 | 3m 19s | $0.93 |
| xcodebuildmcp-test-boundary-review | 0 | 2m 34s | $11.91 |
| xcodebuildmcp-tool-contract-review | 0 | 2m 44s | $0.06 |
| wrdn-pii | 0 | 10m 3s | $5.51 |
| wrdn-authz | 0 | 6m 8s | $3.82 |
| wrdn-code-execution | 0 | 6m 55s | $3.72 |
| wrdn-data-exfil | 0 | 10m 48s | $1.35 |
| find-bugs | 4 | 12m 53s | $6.35 |
| code-review | 0 | 13m 46s | $5.26 |
| code-simplifier | 0 | 14m 10s | $1.00 |
Duration: 92m 49s · Tokens: 13.1M in / 38.0k out · Cost: $43.27 (+merge: $0.00, +dedup: $0.02, +extraction: $0.00)