|
| 1 | +# Investigation: Orphaned simctl OSLog processes when MCP server exits abnormally |
| 2 | + |
| 3 | +**Issue:** [#382](https://github.com/getsentry/XcodeBuildMCP/issues/382) |
| 4 | +**Date:** 2026-05-01 |
| 5 | +**Status:** Confirmed and fixed for simulator OSLog helpers. |
| 6 | + |
| 7 | +## Summary |
| 8 | + |
| 9 | +The issue described orphaned simulator helper processes after abnormal server exits. The final fix is deliberately narrow: |
| 10 | + |
| 11 | +- `simctl spawn … log stream …` OSLog helpers are registry-backed and now reconciled safely across MCP/daemon restarts. |
| 12 | +- `simctl launch --console-pty …` remains intentionally unregistered. It is tied to the launched app lifecycle, and adding a durable registry for it would overreach the accepted design. |
| 13 | +- The old `src/utils/log_capture.ts` path was dead production code and has been removed. |
| 14 | + |
| 15 | +The fix does not change public tool schemas. |
| 16 | + |
| 17 | +## Final design |
| 18 | + |
| 19 | +### OSLog helpers are workspace-scoped |
| 20 | + |
| 21 | +Durable OSLog registry records now store owner identity as: |
| 22 | + |
| 23 | +```ts |
| 24 | +owner: { |
| 25 | + instanceId: string; |
| 26 | + pid: number; |
| 27 | + workspaceKey: string; |
| 28 | +} |
| 29 | +``` |
| 30 | + |
| 31 | +The `workspaceKey` is configured during MCP, CLI, and daemon startup from the same workspace-root hashing used by daemon socket paths. Existing internal records without `owner.workspaceKey` are treated as invalid and pruned. No registry versioning or migration was added. |
| 32 | + |
| 33 | +### Reconciliation only reaps safe orphans |
| 34 | + |
| 35 | +Startup reconciliation runs for both MCP server startup and daemon startup. It scans durable OSLog records and only stops a helper when all of these are true: |
| 36 | + |
| 37 | +1. The record belongs to the current workspace. |
| 38 | +2. The owner PID is not the current process. |
| 39 | +3. The owner PID is no longer alive. |
| 40 | +4. The helper process still matches the expected `simctl spawn … log stream …` command. |
| 41 | + |
| 42 | +Records from other workspaces are skipped. Records owned by live processes are skipped, including live foreign MCP/daemon sessions in the same workspace. |
| 43 | + |
| 44 | +### App-scoped cleanup is no longer global |
| 45 | + |
| 46 | +`stop_app_sim` and relaunch cleanup still clean OSLog helpers for the target simulator/bundle, but only when the record is: |
| 47 | + |
| 48 | +- owned by the current runtime instance, or |
| 49 | +- in the same workspace with a dead owner PID. |
| 50 | + |
| 51 | +This prevents one active session from killing another active session’s OSLog helper. |
| 52 | + |
| 53 | +### Last-chance cleanup covers local live helpers |
| 54 | + |
| 55 | +MCP lifecycle and daemon lifecycle now install synchronous `exit` cleanup for in-process OSLog sessions. This only signals locally known child processes; it does not perform async registry deletion. If a helper survives, the next startup reconciliation can reap it from the durable registry. |
| 56 | + |
| 57 | +Daemon crash handling was also brought closer to MCP parity with `uncaughtException` and `unhandledRejection` handling that requests shutdown with a non-zero exit code. |
| 58 | + |
| 59 | +### Dead log-capture path removed |
| 60 | + |
| 61 | +`src/utils/log_capture.ts` duplicated old simulator log-capture behavior, but there were no production callers. It also left misleading lifecycle/status fields such as `activeLogSessions` and `simulatorLogSessionCount`. |
| 62 | + |
| 63 | +Removed: |
| 64 | + |
| 65 | +- `src/utils/log_capture.ts` |
| 66 | +- `src/utils/__tests__/log_capture.test.ts` |
| 67 | +- `src/utils/__tests__/log_capture_escape.test.ts` |
| 68 | +- legacy re-exports from `src/utils/log-capture/index.ts` |
| 69 | +- legacy shutdown/status references to `stopAllLogCaptures`, `activeLogSessions`, and `simulatorLogSessionCount` |
| 70 | + |
| 71 | +## Why console-PTY is not registered |
| 72 | + |
| 73 | +`simctl launch --console-pty --terminate-running-process …` is app-lifecycle-bound. It exists to capture the launched app’s stdout/stderr while the app is running. The accepted fix avoids adding a console-PTY registry because that would create a second durable lifecycle model for a helper whose lifetime is already coupled to the app launch. |
| 74 | + |
| 75 | +The strategic cleanup surface is the OSLog helper registry, because OSLog helpers are detached, long-lived, and already have durable records that can be reconciled after abnormal exits. |
| 76 | + |
| 77 | +## Verification expectations |
| 78 | + |
| 79 | +Automated coverage should verify: |
| 80 | + |
| 81 | +- OSLog registry records require `owner.workspaceKey`. |
| 82 | +- records without `owner.workspaceKey` are pruned. |
| 83 | +- same-workspace dead-owner OSLog helpers are reconciled. |
| 84 | +- other-workspace records are skipped. |
| 85 | +- same-workspace live-owner records are skipped. |
| 86 | +- app-scoped cleanup skips live foreign owners. |
| 87 | +- lifecycle snapshots and session status do not expose deleted legacy log-capture fields. |
| 88 | +- synchronous exit cleanup signals only live local OSLog helpers. |
| 89 | + |
| 90 | +Manual smoke testing should verify: |
| 91 | + |
| 92 | +1. Clean shutdown stops helpers owned by the current server. |
| 93 | +2. After `SIGKILL` of an MCP server, restarting in the same workspace reaps the orphaned `simctl spawn … log stream …` helper. |
| 94 | +3. Two live sessions in the same workspace do not kill each other’s OSLog helpers. |
| 95 | +4. Startup from workspace A does not kill workspace B helpers. |
| 96 | +5. `stop_app_sim` does not kill live foreign-owner OSLog helpers. |
| 97 | + |
| 98 | +## Known boundary |
| 99 | + |
| 100 | +This fix does not promise to kill every possible `simctl launch --console-pty` process. That helper is intentionally not part of the durable OSLog registry. The implemented production safety guarantee is: workspace-scoped OSLog helpers are cleaned up without killing helpers owned by other live sessions. |
0 commit comments