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:
- Resolve
simulatorName -> id via resolveSimulatorNameToId.
- If the resolved id equals the committed
simulatorId, do nothing.
- 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.
Context
simulatorNameis the stable, canonical identity for a simulator selection.simulatorIdin.xcodebuildmcp/config.yamlis 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
refreshSimulatorDefaultsinsrc/utils/simulator-defaults-refresh.tsonly reconciles when exactly one ofsimulatorId/simulatorNameis present: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 committedsimulatorIdis kept in the session store as-is, and downstreamresolveSimulatorIdOrName(src/utils/simulator-resolver.ts:92-113) short-circuits on it without validating againstsimctl, so the stale UUID is passed directly toxcrun simctlon any machine that doesn't happen to have that UUID.Expected behaviour
At MCP runtime bootstrap (
hydrateSessionDefaultsForMcpinsrc/runtime/bootstrap-runtime.ts), when bothsimulatorIdandsimulatorNameare present:simulatorName-> id viaresolveSimulatorNameToId.simulatorId, do nothing.simulatorId(and recomputesimulatorPlatformas the existing branches do) in the session store viasessionStore.setDefaultsIfRevisionForProfilewithpersist: false.The
persist: falseis critical — this is an in-memory override per contributor, not a write-back.Proposed fix
Extend
refreshSimulatorDefaultswith a third branch that handles the both-set case, reusingresolveSimulatorNameToId. Add unit tests insrc/utils/__tests__/covering:simulatorId; no call topersistSessionDefaultsPatch.simctllookup fails — existingsimulatorIdretained, 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.