Skip to content

Simulator defaults refresh does not reconcile when both simulatorId and simulatorName are set #357

@cameroncooke

Description

@cameroncooke

Context

simulatorName is the stable, canonical identity for a simulator selection. simulatorId in .xcodebuildmcp/config.yaml is allowed as a cached/convenience value (useful for sole developers), but it is expected to be reconciled in memory on each contributor's machine so a committed UUID does not break tools when cloned to a different environment. This reconciliation intentionally never writes back to disk — we don't want config churn from contributors each having different local simulator UUIDs.

Bug

refreshSimulatorDefaults in src/utils/simulator-defaults-refresh.ts only reconciles when exactly one of simulatorId / simulatorName is present:

if (!simulatorId && simulatorName) {
  // name -> id, in-memory
}
if (!simulatorName && simulatorId) {
  // id -> name, in-memory
}

When both are set in the project config (the common and recommended shape, e.g. example_projects/iOS_Calculator/.xcodebuildmcp/config.yaml), neither branch fires. The stale committed simulatorId is kept in the session store as-is, and downstream resolveSimulatorIdOrName (src/utils/simulator-resolver.ts:92-113) short-circuits on it without validating against simctl, so the stale UUID is passed directly to xcrun simctl on any machine that doesn't happen to have that UUID.

Expected behaviour

At MCP runtime bootstrap (hydrateSessionDefaultsForMcp in src/runtime/bootstrap-runtime.ts), when both simulatorId and simulatorName are present:

  1. Resolve simulatorName -> id via resolveSimulatorNameToId.
  2. If the resolved id equals the committed simulatorId, do nothing.
  3. If it differs, patch simulatorId (and recompute simulatorPlatform as the existing branches do) in the session store via sessionStore.setDefaultsIfRevisionForProfile with persist: false.

The persist: false is critical — this is an in-memory override per contributor, not a write-back.

Proposed fix

Extend refreshSimulatorDefaults with a third branch that handles the both-set case, reusing resolveSimulatorNameToId. Add unit tests in src/utils/__tests__/ covering:

  • Both set, name resolves to the same id — no patch applied.
  • Both set, name resolves to a different id — in-memory patch updates simulatorId; no call to persistSessionDefaultsPatch.
  • Both set, simctl lookup fails — existing simulatorId retained, warning logged.

Surfaced by

Cursor Bugbot review on #349 (#349 (comment)), though the bot's framing (committed UUID is a regression) differs from the actual root cause captured here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions