Skip to content

feat: Workspace filesystem cleanup

87c486d
Select commit
Loading
Failed to load commit list.
Merged

feat: Workspace filesystem cleanup #391

feat: Workspace filesystem cleanup
87c486d
Select commit
Loading
Failed to load commit list.
@sentry/warden / warden completed May 4, 2026 in 16m 18s

6 issues

Medium

Build Logs path not updated to workspace-scoped path - `src/snapshot-tests/__fixtures__/cli/macos/test--success.txt:20`

The Derived Data path was updated to the new workspace-scoped layout (workspaces/XcodeBuildMCP-<HASH>/DerivedData/...), but the Build Logs path on the same fixture still points to the old top-level logs/ directory. The PR description states logs are also moved into workspace-keyed paths under the shared application support location, so MCP, CLI, and JSON fixtures should consistently reflect the workspace-scoped log path. If logs are now workspace-scoped at runtime, this fixture would drift from rendered output and tests would fail; if not, the two paths in this fixture are inconsistent with the stated change.

Also found at:

  • src/snapshot-tests/__fixtures__/cli/simulator/build-and-run--error-compiler.txt:9
  • src/snapshot-tests/__fixtures__/cli/device/build--error-wrong-scheme.txt:8
  • src/snapshot-tests/__fixtures__/mcp/simulator/test--failure.txt:37
Volatile per-run progress lines added directly to fixture instead of being normalized - `src/snapshot-tests/__fixtures__/cli/swift-package/test--failure.txt:8-14`

The updated fixture now embeds 'Running tests (N completed, M failure, 0 skipped)' progress lines that depend on test execution timing/ordering. Per the skill's guardrails, volatile values should be normalized in code rather than patched ad hoc into fixtures. If these lines are inherently nondeterministic across runs, they should be filtered or normalized in the snapshot pipeline; otherwise future runs may produce intermittent fixture diffs forcing fixture-only updates to make tests pass.

Daemon socket directory in shared tmpdir is predictable and vulnerable to local pre-creation/symlink attacks - `src/daemon/socket-path.ts:29-36`

daemonDirForWorkspaceKey now constructs <tmpdir>/xcodebuildmcp-<12-hex-of-sha256(workspaceRoot)>. On Linux/macOS tmpdir() is typically /tmp, a world-writable directory shared across local users. The 12-hex suffix is fully deterministic from the (often guessable) workspace root path, so a co-resident local user can pre-create the directory (or a symlink at that name) before the victim's daemon starts. ensureSocketDir calls mkdirSync(dir, { recursive: true, mode: 0o700 }), but Node's mkdirSync does not modify mode on an existing directory, so the 0o700 protection is silently bypassed when the path already exists. Combined with removeStaleSocket blindly unlinkSync-ing inside that directory, a local attacker can squat on the path, intercept/disrupt the daemon's IPC socket, or force the daemon to bind into an attacker-controlled location. The previous implementation placed these under ~/.xcodebuildmcp/daemons/<key>, which is inside the user's home directory and not exposed to other local users.

isPidAlive treats EPERM as alive but ignores invalid pid 0/negative which targets process group - `src/utils/process-liveness.ts:1-8`

process.kill(pid, 0) with pid <= 0 has special semantics in POSIX: pid 0 signals every process in the caller's process group, and negative pids signal a process group. Passing such values to isPidAlive will not check liveness of a specific process and may return true based on group membership rather than the intended target. Callers using this for live-owner detection during cleanup could incorrectly conclude that a stale/invalid recorded pid (e.g., 0) is still alive and skip cleanup of artifacts indefinitely.

Startup registry lock leaks if setup throws before server.listen - `src/daemon.ts:186-197`

The startup registry lock is acquired at line 186 but is only released inside the server.listen callback (line 433) or via server.on('error') (line 412). If any code between acquisition and server.listen—such as buildDaemonToolCatalogFromManifest (line 270), setSentryRuntimeContext, recordDaemonGaugeMetric, or startDaemonServer—throws, control falls through to the top-level main().catch which exits without releasing the lock. Depending on the lock implementation, this can leave a stale lock that blocks subsequent daemon startups for the workspace.

Low

removeDaemonRegistryEntry and cleanupWorkspaceDaemonFiles silently swallow lock-acquisition failures - `src/daemon/daemon-registry.ts:286-345`

withDaemonRegistryMutationLock returns null when the bounded busy-wait for the daemon registry lock times out (DAEMON_REGISTRY_LOCK_WAIT_MS = 1s). writeDaemonRegistryEntry checks for this null and throws, but removeDaemonRegistryEntry (lines 286-288) and cleanupWorkspaceDaemonFiles (lines 331-345) discard the return value. Under contention from another MCP server, daemon, or CLI, cleanup will silently no-op, leaving stale registry/socket files behind that can mislead subsequent daemon startup or liveness checks.

14 skills analyzed
Skill Findings Duration Cost
xcodebuildmcp-docs-release-review 0 6.5s $0.02
xcodebuildmcp-docs-command-review 0 6.4s $0.08
xcodebuildmcp-runtime-boundary-review 0 1m 5s $0.71
xcodebuildmcp-snapshot-fixture-review 2 3m 13s $9.34
xcodebuildmcp-structured-output-review 0 52.7s $2.84
xcodebuildmcp-test-boundary-review 0 5m 44s $3.06
xcodebuildmcp-tool-contract-review 0 59.6s $0.17
wrdn-pii 0 10m $16.72
wrdn-authz 0 10m 51s $1.46
wrdn-code-execution 0 11m 41s $1.36
wrdn-data-exfil 0 6m 37s $3.71
find-bugs 3 15m 52s $4.48
code-review 1 13m 59s $2.74
code-simplifier 0 12m 33s $3.48

Duration: 93m 41s · Tokens: 13.1M in / 38.6k out · Cost: $50.21 (+merge: $0.00, +dedup: $0.04, +extraction: $0.00)