Skip to content

Conversation

@andreyz
Copy link
Contributor

@andreyz andreyz commented Oct 2, 2025

Summary

  • Fixed list_sims tool to show iOS 26.0 simulators by working around Apple simctl JSON bug
  • Implemented hybrid JSON + text parsing approach to capture all available simulators

Background/Details

Root Cause Analysis

Apple's simctl list devices --json has a bug when multiple runtime versions share the same identifier. In the case of iOS 26.0, there are two beta builds (23A339 and 23A343) that both use the identifier com.apple.CoreSimulator.SimRuntime.iOS-26-0.

When simctl generates JSON output with duplicate object keys, the second key's value overwrites the first, leaving one runtime's device array empty. The text output correctly shows both sections with all devices, but the JSON output loses devices from one of the duplicate runtimes.

This is an Apple bug in simctl, not an issue with XcodeBuildMCP's implementation.

Solution

Implemented a robust hybrid parsing strategy:

  1. Parse JSON first (primary source) - Reliable for most runtimes and provides structured data
  2. Parse text output (fallback) - Captures devices that may be missing from JSON due to duplicate runtime IDs
  3. Merge intelligently - Combine results by UUID, adding any devices from text that aren't in JSON

The text parser uses regex to extract device information from the plain text output format, handling all simulator states (Booted, Shutdown, etc.) and filtering out unavailable devices.

Testing

Test Coverage

  • ✅ All 10 existing tests pass
  • ✅ Updated tests to handle dual executor calls (JSON + text commands)
  • ✅ Added test for JSON parse failure with text fallback
  • ✅ TypeScript compilation passes with no errors
  • ✅ ESLint passes with no warnings

Manual Testing

Verified on system with iOS 26.0 beta simulators that were previously hidden:

  • Before: Only iOS 18.6 simulators shown
  • After: Both iOS 18.6 and iOS 26.0 simulators shown (15 devices total)

Validation Method

npm run typecheck  # ✅ Pass
npm run lint       # ✅ Pass  
npm run build      # ✅ Pass
npm test           # ✅ Pass (10/10 tests)

Notes

  • This fix is backwards compatible and doesn't affect behavior for systems without duplicate runtime IDs
  • The text parser is resilient to different UUID formats (supports both real UUIDs and test UUIDs)
  • Performance impact is minimal as text parsing only processes output when JSON is insufficient
  • This workaround will remain necessary until Apple fixes the duplicate runtime ID issue in a future Xcode/simctl release

Summary by CodeRabbit

  • New Features

    • Simulator list now queries JSON first, falls back to text, and merges results to include devices missing from JSON.
    • Display grouped by runtime, marks booted devices, and adds a “Next Steps” section.
  • Bug Fixes

    • Robust fallback when JSON parsing fails; excludes unavailable devices and preserves consistent error text.
  • Tests

    • Expanded coverage for JSON/text paths, merged results, booted/shutdown states, parse-fallback, and failure scenarios.

Note

Implement JSON-first with text fallback and merge in list_sims, adding text parsing and updating tests to cover fallback, merging, and dual-command execution.

  • Tools/simulator:
    • Implement hybrid parsing in list_sims.ts: call simctl ... --json first, then text command, parse text via parseTextOutput, and merge devices by udid (preferring JSON).
    • Add runtime to SimulatorDevice; filter unavailable devices; preserve booted marker; keep “Next Steps” output.
    • Adjust commands and logging: List Simulators (JSON) and List Simulators (Text); improved error handling when JSON parse fails.
  • Tests:
    • Update list_sims tests to expect two executor calls (JSON + text), verify booted display, JSON-parse fallback, and merging devices missing from JSON.
    • Update simulators resource test to expect fallback behavior when JSON is invalid and confirm parsed text content.

Written by Cursor Bugbot for commit 1f8edcf. This will update automatically on new commits. Configure here.

Work around Apple simctl JSON bug where duplicate runtime IDs
(multiple iOS 26.0 betas with same com.apple.CoreSimulator.SimRuntime.iOS-26-0
identifier) cause JSON output collision, leaving one runtime's devices as empty array.

Solution: Hybrid parsing approach that combines JSON (primary) and text (fallback)
outputs, merging results to capture all available simulators across all runtimes.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 2, 2025

Walkthrough

Attempts JSON simulator listing first and falls back to parsing plain-text output on parse/error. Adds parseTextOutput, an optional runtime on devices, merges JSON and text-derived devices (de-duplicating by UUID), filters unavailable devices, and formats grouped output by runtime with boot states and a “Next Steps” section. Tests updated.

Changes

Cohort / File(s) Summary
Simulator listing logic
src/mcp/tools/simulator/list_sims.ts
Adds optional runtime to SimulatorDevice; implements parseTextOutput(text); updates list_simsLogic to call simctl ... --json first, fall back to plain text on JSON parse/error, merge JSON and text devices (de-dup by UUID), exclude unavailable devices, and format output grouped by runtime with Booted/Shutdown markers and a "Next Steps" section.
Simulator unit tests (tools)
src/mcp/tools/simulator/__tests__/list_sims.test.ts
Replaces single-output mocks with a branching mock executor returning JSON when --json is present and text otherwise; introduces mockJsonOutput and mockTextOutput; updates list_simsLogic invocation to use the new executor; updates expectations for two invocations (JSON then text); adds tests for booted/shutdown formatting, merging devices when JSON lacks entries, JSON-parse-fallback to text, and various error paths.
Simulator resource tests
src/mcp/resources/__tests__/simulators.test.ts
Expands JSON-parse-failure test to use a mixed executor that returns invalid JSON for --json and valid text otherwise; verifies fallback to text parsing and updated formatted output assertions.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant ListLogic as list_simsLogic
  participant Executor as "simctl Executor"
  participant JSONParser as "JSON parse"
  participant TextParser as "parseTextOutput"
  participant Formatter

  User->>ListLogic: request list simulators
  ListLogic->>Executor: xcrun simctl list devices --json
  Executor-->>ListLogic: stdout (JSON or invalid) / error
  alt JSON parse succeeds
    ListLogic->>JSONParser: parse JSON
    JSONParser-->>ListLogic: devices(JSON)
  else JSON parse fails or Executor error
    ListLogic->>Executor: xcrun simctl list devices
    Executor-->>ListLogic: stdout (text) / error
    ListLogic->>TextParser: parse text
    TextParser-->>ListLogic: devices(text)
  end
  note right of ListLogic: Merge devices (prefer JSON, de-dup by UUID)
  ListLogic->>Formatter: group by runtime, filter unavailable, mark Booted/Shutdown
  Formatter-->>User: formatted device list + "Next Steps"
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

I sniffed the sims, both JSON and plain,
When one path failed, I hopped back again.
Runtimes gathered, UUIDs aligned,
Booted flags gleam, shutdowns signed.
Next steps set—my warren's well-defined. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly summarizes the primary change by stating that it fixes handling of missing iOS 26.0 simulators in the list_sims output, directly reflecting the core issue and resolution implemented in this PR.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@andreyz
Copy link
Contributor Author

andreyz commented Oct 2, 2025

Cursor review

@cursor
Copy link
Contributor

cursor bot commented Oct 2, 2025

Skipping Bugbot: Unable to authenticate your request. Please make sure Bugbot is properly installed and configured for this repository.

@andreyz
Copy link
Contributor Author

andreyz commented Oct 2, 2025

@cameroncooke please have a look

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/mcp/tools/simulator/list_sims.ts (1)

28-63: Verify indentation stability and consider more robust parsing.

The text parser makes several assumptions that could be fragile:

  1. Line 44-46: The device regex requires exactly 4 spaces (^\s{4}). If Apple changes simctl's indentation, parsing will break silently.
  2. Line 47: Devices can be added to the list even if currentRuntime is empty (before any runtime header is seen). This could result in devices with runtime: '' being added.
  3. Line 49: The unavailable check uses line.includes('unavailable') which could theoretically match device names containing "unavailable" (though unlikely in practice).

Consider these improvements:

 function parseTextOutput(textOutput: string): SimulatorDevice[] {
   const devices: SimulatorDevice[] = [];
   const lines = textOutput.split('\n');
   let currentRuntime = '';
 
   for (const line of lines) {
     // Match runtime headers like "-- iOS 26.0 --" or "-- iOS 18.6 --"
     const runtimeMatch = line.match(/^-- ([\w\s.]+) --$/);
     if (runtimeMatch) {
       currentRuntime = runtimeMatch[1];
       continue;
     }
 
     // Match device lines like "    iPhone 17 Pro (UUID) (Booted)"
     // UUID pattern is flexible to handle test UUIDs like "test-uuid-123"
     const deviceMatch = line.match(
-      /^\s{4}(.+?)\s+\(([^)]+)\)\s+\((Booted|Shutdown|Booting|Shutting Down)\)(?:\s+\(unavailable.*\))?$/i,
+      /^\s+(.+?)\s+\(([^)]+)\)\s+\((Booted|Shutdown|Booting|Shutting Down)\)(\s+\(unavailable.*\))?$/i,
     );
-    if (deviceMatch && currentRuntime) {
+    if (deviceMatch && currentRuntime) {
       const [, name, udid, state] = deviceMatch;
-      const isUnavailable = line.includes('unavailable');
+      const isUnavailable = Boolean(deviceMatch[4]);
       if (!isUnavailable) {
         devices.push({
           name: name.trim(),
           udid,
           state,
           isAvailable: true,
           runtime: currentRuntime,
         });
       }
     }
   }
 
   return devices;
 }

Changes:

  • Replace \s{4} with \s+ to accept any indentation
  • Capture unavailable suffix in group 4 and check it directly instead of line.includes()
  • The currentRuntime guard on line 47 already prevents devices without runtime
src/mcp/tools/simulator/__tests__/list_sims.test.ts (1)

51-131: LGTM, but consider adding a merge scenario test.

The test correctly verifies the dual-path execution and updated output format. However, this test has identical devices in both JSON and text outputs, so it doesn't exercise the merge logic that adds text-only devices (the core bug fix).

Consider adding a test where the text output contains devices NOT present in the JSON output to verify the merge logic:

it('should merge devices from text that are missing from JSON', async () => {
  const mockJsonOutput = JSON.stringify({
    devices: {
      'iOS 18.6': [
        {
          name: 'iPhone 15',
          udid: 'json-uuid-123',
          isAvailable: true,
          state: 'Shutdown',
        },
      ],
    },
  });

  const mockTextOutput = `== Devices ==
-- iOS 18.6 --
    iPhone 15 (json-uuid-123) (Shutdown)
-- iOS 26.0 --
    iPhone 17 Pro (text-uuid-456) (Shutdown)`;

  const mockExecutor = async (command: string[]) => {
    if (command.includes('--json')) {
      return { success: true, output: mockJsonOutput, error: undefined, process: { pid: 12345 } };
    }
    return { success: true, output: mockTextOutput, error: undefined, process: { pid: 12345 } };
  };

  const result = await list_simsLogic({ enabled: true }, mockExecutor);

  // Should contain both iOS 18.6 from JSON and iOS 26.0 from text
  expect(result.content[0].text).toContain('iOS 18.6:');
  expect(result.content[0].text).toContain('iPhone 15 (json-uuid-123)');
  expect(result.content[0].text).toContain('iOS 26.0:');
  expect(result.content[0].text).toContain('iPhone 17 Pro (text-uuid-456)');
});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1beefab and 666ba0c.

📒 Files selected for processing (2)
  • src/mcp/tools/simulator/__tests__/list_sims.test.ts (5 hunks)
  • src/mcp/tools/simulator/list_sims.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{test,spec}.ts: Do not use Vitest mocking APIs in tests (no vi.mock, vi.fn, vi.spyOn, etc.)
In tests, import the core ...Logic function from the tool and use createMockExecutor/createMockFileSystemExecutor to simulate outcomes

Files:

  • src/mcp/tools/simulator/__tests__/list_sims.test.ts
**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

**/*.ts: Use .ts extensions for all internal relative imports (e.g., import { tool } from './tool.ts')
Use .ts extensions in re-exports for internal files (e.g., export { default } from '../shared/tool.ts')
Use .js extension only for external package entry points (e.g., import ... from '@scope/pkg/file.js')
Never import internal project files using .js extensions

Files:

  • src/mcp/tools/simulator/__tests__/list_sims.test.ts
  • src/mcp/tools/simulator/list_sims.ts
🧠 Learnings (1)
📚 Learning: 2025-09-21T19:27:22.795Z
Learnt from: CR
PR: cameroncooke/XcodeBuildMCP#0
File: AGENTS.md:0-0
Timestamp: 2025-09-21T19:27:22.795Z
Learning: Applies to **/*.{test,spec}.ts : In tests, import the core ...Logic function from the tool and use createMockExecutor/createMockFileSystemExecutor to simulate outcomes

Applied to files:

  • src/mcp/tools/simulator/__tests__/list_sims.test.ts
🧬 Code graph analysis (2)
src/mcp/tools/simulator/__tests__/list_sims.test.ts (1)
src/mcp/tools/simulator/list_sims.ts (1)
  • list_simsLogic (102-214)
src/mcp/tools/simulator/list_sims.ts (1)
src/utils/logger.ts (1)
  • log (142-162)
🔇 Additional comments (11)
src/mcp/tools/simulator/list_sims.ts (6)

21-21: LGTM: Optional runtime field.

The optional runtime field appropriately supports text-parsed devices while maintaining backward compatibility with JSON-only parsing.


109-122: LGTM: Early return on JSON command failure.

Properly exits early when the JSON command fails, avoiding unnecessary text parsing in true error scenarios.


124-133: LGTM: Graceful JSON parse failure handling.

The try-catch correctly handles invalid JSON from simctl (the core bug this PR addresses) and falls back to text parsing with appropriate logging.


135-167: LGTM: Hybrid merge strategy correctly implemented.

The merge logic properly:

  • Collects UUIDs from available JSON devices to avoid duplicates
  • Adds text-parsed devices missing from JSON (the Apple bug workaround)
  • Logs when devices are added from text parsing for observability
  • Handles missing runtime with sensible fallback

This directly addresses the iOS 26.0 duplicate runtime ID bug described in the PR objectives.


169-193: LGTM: Enhanced output formatting.

The output formatting improvements are well-implemented:

  • Groups devices by runtime for clarity
  • Shows boot state with [Booted] indicator
  • Includes helpful "Next Steps" section for user guidance
  • Filters to only available devices

202-213: LGTM: Proper exception handling.

The catch block correctly handles both Error objects and other thrown values, logs appropriately, and returns a well-formatted error response.

src/mcp/tools/simulator/__tests__/list_sims.test.ts (5)

1-48: LGTM: Test setup and schema validation.

The test structure correctly follows the coding guidelines by importing the core list_simsLogic function and using mock executors. Schema validation tests remain comprehensive and appropriate.

Based on learnings.


133-187: LGTM: Booted state test.

Correctly verifies that booted simulators display the [Booted] indicator in the output.


189-207: LGTM: Command failure test.

Properly verifies error handling when the simulator command fails, using the mock executor helper as specified in coding guidelines.

Based on learnings.


209-254: LGTM: JSON parse failure fallback test.

This test correctly exercises the core workaround for the Apple simctl bug: when JSON parsing fails, the implementation falls back to text parsing and produces valid output. This is essential coverage for the bug described in the PR objectives.


256-284: LGTM: Exception handling tests.

Both tests appropriately verify that exceptions (Error objects and string errors) are handled correctly and produce proper error messages.

- Replace fixed-width indentation (\s{4}) with flexible indentation (\s+)
- Capture unavailable suffix in regex group instead of line.includes()
- Add test for merge logic with text-only devices (core bug fix scenario)
- Update resource test to handle new fallback behavior

Addresses CodeRabbit review feedback on PR getsentry#118
@andreyz
Copy link
Contributor Author

andreyz commented Oct 2, 2025

Addressed CodeRabbit review feedback:

  1. Text parser robustness: Changed regex from \s{4} to \s+ for flexible indentation handling and capture unavailable suffix directly in regex group instead of using line.includes()

  2. Test coverage: Added test case that verifies the core merge logic where devices from text output that aren't in JSON are properly added (the actual iOS 26.0 bug fix scenario)

  3. Resource test update: Updated simulators resource test to match new fallback behavior

All quality checks pass (typecheck, lint, 1105 tests).

@cameroncooke
Copy link
Collaborator

@cursor review

@pkg-pr-new
Copy link

pkg-pr-new bot commented Oct 2, 2025

Open in StackBlitz

npm i https://pkg.pr.new/cameroncooke/XcodeBuildMCP/xcodebuildmcp@118

commit: 1f8edcf

-- iOS 17.0 --
iPhone 15 (test-uuid-123) (Booted)`;

const mockExecutor = async (command: string[]) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Mock Executor Signature Mismatch

The mock executors in list_simsLogic and simulatorsResourceLogic tests have a signature mismatch. They're defined to only accept command: string[], but the functions under test call the executor with additional parameters like logPrefix, useShell, and env. This divergence from the CommandExecutor interface could lead to unexpected test behavior or runtime errors.

Additional Locations (2)

Fix in Cursor Fix in Web

@cameroncooke
Copy link
Collaborator

@andreyz Thanks for this. My gut feeling is that this isn't a bug at all, but rather a data issue. JSON should have unique keys; hence, the last value associated with a pre-existing key will prevail. I'm trying to understand the use-case here, but having two simulators with the same identifier seems like an edge case based on the use of multiple beta builds. My preference is to avoid adding additonal complexity here. Could you give me an example of why it's important for XcodeBuildMCP to be able to work with multiple beta builds of the same iOS release and why would you need to test against older beta builds?

@andreyz
Copy link
Contributor Author

andreyz commented Oct 2, 2025

@cameroncooke the issue that I ran into was that I had three iOS 26 runtimes installed (two betas and one release) and one 18.6. Given that list_sims would see only the iOS 18.6 there was no way for me to run a build on any of the iOS 26 sims.

@andreyz
Copy link
Contributor Author

andreyz commented Oct 2, 2025

I know it's an edge case issue caused by JSON output of xcrun simctl tool, generating incomplete data, not something that's at fault with xcodemcp. I defer to you the decision to integrate or ignore.

The patch would make your tool more resilient to faulty output. But the intense beta period won't repeat until next year, so probably no one else will hit that issue.

@andreyz
Copy link
Contributor Author

andreyz commented Oct 2, 2025

Forgot to answer: testing regressions between beta builds is valuable when keeping track of issues resolving and popping back during the pre-release period.
Not to mention that simulator runtimes tend to pile up, as one need to manually remove them.

Copy link
Collaborator

@cameroncooke cameroncooke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I'm going to approve as the changes are fairly small. Thanks for the contribution @andreyz

@cameroncooke cameroncooke merged commit 059b91c into getsentry:main Oct 2, 2025
6 of 7 checks passed
@andreyz
Copy link
Contributor Author

andreyz commented Oct 2, 2025

Thank you for creating and maintaining this tool!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants