feat(log-capture): Durable OSLog session tracking and executor settlement fixes#336
feat(log-capture): Durable OSLog session tracking and executor settlement fixes#336cameroncooke wants to merge 6 commits intomainfrom
Conversation
Route shared xcodebuild snapshot scenarios through canonical CLI fixtures and make MCP durable text rendering use the same non-interactive transcript formatter. This removes MCP-specific newline drift and keeps test discovery formatting consistent across success and failure paths. Also surface selective test targeting in test headers and limit discovery previews to the first six tests so the canonical fixtures stay readable while preserving parity across runtimes. Co-Authored-By: OpenAI Codex <noreply@openai.com>
…e alone The close event can be delayed indefinitely when a detached grandchild inherits stdout/stderr file descriptors. Replace the single close listener with a state machine that tracks open streams, observes exit, and settles when both conditions are met. A 100ms safety timer after exit handles the case where streams never drain. The close event remains a final authority that forces immediate settlement.
…ions When the executor throws after startBuildPipeline has been called, the pipeline response was left in a pending state with no output. Hoist the started variable above the try block so the catch handler can call finalizeInlineXcodebuild and produce a proper error response with build log links.
…ess registry Detached simctl log stream helper processes were spawned fire-and-forget with no tracking, accumulating silently across server restarts. Add a filesystem-backed registry under ~/Library/Developer/XcodeBuildMCP/state/ that records each helper's PID, owner instance, and expected command signature. Liveness is verified via ps command matching to handle PID recycling. On app launch, existing sessions for the same simulator+bundleId are cleaned up before spawning a new helper. On app stop, tracked sessions are terminated alongside the simctl terminate call. On server shutdown, only sessions owned by the current process are stopped. The lifecycle snapshot and session status resource expose tracked session counts for observability.
commit: |
src/rendering/render.ts
Outdated
| return contentParts.join(''); | ||
| return renderCliTextTranscript(events, { | ||
| suppressWarnings: suppressWarnings ?? false, | ||
| }); |
There was a problem hiding this comment.
Text strategy now always renders next-steps as CLI format
Medium Severity
The createTextRenderSession (the 'text' strategy used for MCP tool responses) now delegates to renderCliTextTranscript, which internally uses createCliTextProcessor. That processor always passes 'cli' to formatNextStepsEvent(event, 'cli'), ignoring the event's runtime property. Previously, the old inline rendering in createTextRenderSession honored it: const effectiveRuntime = event.runtime === 'cli' ? 'cli' : 'mcp'. This means MCP tool responses now show CLI-style commands (e.g. xcodebuildmcp macos get-app-path) instead of MCP tool invocations (e.g. get_mac_app_path({ ... })), which are what AI agents using the MCP protocol can actually call.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit afa79db. Configure here.
Track simulator launch OSLog helpers in a durable registry so stop and shutdown can clean them up across independent CLI and MCP process lifetimes. Keep lifecycle visibility accurate for parallel runs, prune stale or corrupt registry entries, and tighten the related command and rendering refactors that support the new ownership model. Refs GH-273 Co-Authored-By: OpenAI Codex <noreply@openai.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit f3bbe56. Configure here.
| }) as ChildProcess['kill']; | ||
|
|
||
| return child; | ||
| } |
There was a problem hiding this comment.
Duplicated createTrackedChild helper across three test files
Low Severity
The createTrackedChild helper function is independently re-implemented in three test files (stop_app_sim.test.ts, session-status.test.ts, and mcp-lifecycle.test.ts) with slightly different signatures and behaviors. This increases maintenance burden — future changes to the mock ChildProcess shape need to be synchronized across all three copies. A shared test utility (e.g., in test-utils/) would reduce duplication.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit f3bbe56. Configure here.
Batch registry PID sampling so durable OSLog cleanup does less process churn while keeping stale-entry pruning behavior unchanged. Make the test-only session reset helper explicit, guard simulator device parsing against malformed simctl payloads, and avoid duplicate local cleanup when both child exit and close fire. These changes keep the durable registry design the same while tightening a few rough edges called out in follow-up review. Co-Authored-By: OpenAI Codex <noreply@openai.com>


Add a filesystem-backed registry for tracking detached
simctl spawn ... log streamhelper processes across MCP server restarts, plus fixes for command executor settlement and test pipeline error handling.OSLog session tracking architecture
The core problem: every
build_run_simlaunch spawns a detachedsimctl spawn <uuid> log streamprocess to capture app logs. These helpers were fire-and-forget -- no tracking, no cleanup on re-launch, no cleanup on app stop, and invisible to other MCP server instances. Over time they accumulate silently.The solution is a durable, filesystem-backed registry under
~/Library/Developer/XcodeBuildMCP/state/simulator-launch-oslog/. Each helper gets a JSON record written atomically (write-to-temp, rename) containing the helper PID, owner instance identity, simulator UUID, bundle ID, and expected command signature.Key design decisions:
RuntimeInstanceidentity (UUID + PID) tags each record's owner. Any MCP server process can read all records, but only stops its own sessions during shutdown. This prevents one server from killing another's active log streams.ps -p <pid> -o pid=,command=and verify the running command contains expected parts (simctl,spawn,<uuid>,log,stream,<bundleId>). A matching PID with a different command is treated as stale and pruned.listcall prunes malformed JSON files and stale records automatically, so the registry never grows unboundedly even if a process crashes without cleanup.{ stoppedSessionCount, errorCount, errors }for structured reporting.Lifecycle integration points:
stop_app_simterminates tracked OSLog sessions alongside thesimctl terminatecallCommand executor settlement fix
The
defaultExecutorincommand.tsresolved on thecloseevent alone. When a spawned process exits but a detached grandchild inherits stdout/stderr FDs,closeis delayed indefinitely -- hanging the promise. Replaced with a state machine that settles when bothexitis observed and all streams are drained, with a 100ms safety timer as fallback. Thecloseevent remains a final authority for immediate settlement.Test pipeline finalization fix
handleTestLogicintest-common.tscould leave the xcodebuild pipeline response in a pending state if the executor threw afterstartBuildPipeline. Thestartedvariable is now hoisted so the catch handler can callfinalizeInlineXcodebuildand produce a proper error response.