Repository navigation
Description
Problem
stim ios --remote proxy|eas compiles the iOS simulator app for both arm64 and x86_64, although the remote simulator runs only one of them. On apps/mobile that roughly doubles xcodebuild time, .app size and DerivedData size compared with a single-arch build.
Local stim ios Debug builds are not affected: they compile only the host arch.
Separately, the iOS simulator cache key carries no architecture, so artifacts built for different archs share one key, locally and in remote cache providers.
Evidence
Machine: arm64 MacBook, Xcode 27.0 (27A266a), stim origin/main 45eff4b, apps/mobile, 2026-09-28.
Arguments Stim passes to xcodebuild. xcodebuildArgs (packages/stim-cli/src/engine/xcode.ts:385-424) passes -sdk iphonesimulator -destination <destination || id=<udid>>. It never passes ARCHS, ONLY_ACTIVE_ARCH or -arch. The only extra build settings are the compilation-cache settings. Local runs pass id=<owned simulator udid>. --remote passes GENERIC_SIM_DESTINATION = 'generic/platform=iOS Simulator' (packages/stim-cli/src/commands/ios/artifact.ts:51-52, :769, set from remoteDestination: Boolean(remoteDevice) in commands/ios.ts:806).
Existing artifacts in ~/.stim/build-cache/ios/ (lipo -archs on Stim.app/Stim and Stim.app/Frameworks/ExpoCamera.framework/ExpoCamera):
| Entries | Main executable | Framework | .app size |
|---|---|---|---|
9 x <fp>-debug-sim |
arm64 | arm64 | 171-180 MB |
11 x <fp>-release-sim |
x86_64 arm64 | x86_64 arm64 | 130-138 MB |
Resolved build settings (xcodebuild -workspace Stim.xcworkspace -scheme Stim -sdk iphonesimulator -showBuildSettings -json, target Stim):
| Configuration | Destination | Extra settings | ARCHS | ONLY_ACTIVE_ARCH |
|---|---|---|---|---|
| Debug | id=<udid> |
none | arm64 | YES |
| Debug | generic | none | arm64 x86_64 | NO |
| Debug | generic | ARCHS=arm64 ONLY_ACTIVE_ARCH=YES |
arm64 | NO |
| Release | id=<udid> |
none | arm64 x86_64 | NO |
| Release | id=<udid> |
ARCHS=arm64 ONLY_ACTIVE_ARCH=YES |
arm64 | YES |
| Release | generic | none | arm64 x86_64 | NO |
A generic destination forces ONLY_ACTIVE_ARCH=NO even when it is passed on the command line; only an explicit ARCHS narrows it.
A/B build. The A/B ran in a fresh worktree after expo prebuild and pod install. Each run used a fresh -derivedDataPath, Debug configuration, the generic destination, and no compilation cache. It was run once on a heavily loaded machine (load average 135-380 from other agents), so treat the times as indicative.
| Run | xcodebuild | .app |
DerivedData | lipo -archs (exe, framework) |
|---|---|---|---|---|
generic (what --remote does today) |
285 s | 341 MB | 9.7 GB | x86_64 arm64, x86_64 arm64 |
generic + ARCHS=arm64 ONLY_ACTIVE_ARCH=YES |
151 s | 172 MB | 4.4 GB | arm64, arm64 |
This agrees with the offload prototype (#1801) on the Mac mini: 5m07s, 341 MB, 9.9 GB fat vs 2m38s, 171 MB, 4.5 GB arm64 when targeting a concrete simulator.
Cause
--remote. It uses the generic destination because xcodebuild cannot target a remote simulator's UDID. Since #1212 the remote device is also connected only after the build (commands/ios.ts:791-795), so there is no UDID at build time. With a generic destination, Xcode sets ONLY_ACTIVE_ARCH=NO and builds every ARCHS value, which is arm64 x86_64 for the iphonesimulator SDK.
Local Debug. It is single-arch because id=<udid> gives Xcode an active arch and the project's Debug ONLY_ACTIVE_ARCH=YES applies.
Local Release. A local Release simulator build is fat because Release keeps the Xcode default ONLY_ACTIVE_ARCH=NO. #608 treats that as the project's intentional universal policy. It is noted here only for completeness. Narrowing it would override a project setting, which needs its own decision.
Remote simulator arch. Stim cannot learn it today:
- proxy: nothing in
engine/device-remote.tsorengine/agent-device.tsreads host information.agent-device devices --json(0.21.12) reports platform, id, name, kind, target and booted, but no CPU arch. - eas: Stim knows only the requested
deviceType. I did not verify what CPU EAS Simulator hosts run, and did not start a billable session to check.
On a modern Mac the answer is almost always the host arch, since Apple silicon runs arm64 simulators and Rosetta simulators are opt-in. It is still not something Stim observes.
Cache key. buildCacheKey (packages/core/index.ts:168-179) adds an ABI component only for Android. Android uses -debug-sim-arm64-v8a (#394, #961). An iOS simulator key is <fp>-<configuration>-sim[-compiler][-profile][-scheme-...] with no arch, so these artifacts all share one key today:
- a local arm64 Debug build;
- an Intel Mac's x86_64-only local Debug build;
- a fat
--remotebuild.
The remote upload passes only { configuration } as run options (commands/ios/artifact.ts:824-833), so remote providers key the same way. Nothing validates the arch of a cached .app before install: there is no lipo or arch check in packages/stim-cli/src. Today an arm64-only Debug artifact uploaded by one machine can be a remote hit for an x86_64 Mac. That gap already exists independently of --remote, but making --remote single-arch would widen it: a --remote build could then store a single-arch artifact for an arch that differs from the local one.
Fix idea
-
--remote: build only the target arch. Keep the generic destination, because no UDID exists at build time. AppendARCHS=<arch> ONLY_ACTIVE_ARCH=YESas build settings; verified above to narrow a generic build to one arch. Default<arch>to the local host arch (process.arch, mappingx64tox86_64). That is correct whenever the local Mac and the remote host share an arch, which is the common case.- A concrete local simulator as a build-only destination, as feat(build): prototype offloading native iOS builds to another Mac (do not merge) #1801 did on its worker, gives the same result. It also ties the build to the local host arch and needs a local runtime, so it adds nothing over the setting.
- Preserves: no remote UDID needed, and the --remote eas connects the device before the local build, so a build over 5 minutes loses the device's daemon #1212 ordering.
- Trades away: a build that is wrong when the local and remote arch differ (Intel laptop with an arm64 remote Mac, or the reverse). The install or launch would then fail instead of silently working.
-
Detect the remote arch where Stim can.
- proxy: this needs a host-info answer from the agent-device daemon, or a
uname -mit can run. Neither exists in the protocol Stim uses today, so check agent-device before designing around it. - eas: confirm the host CPU from EAS documentation or a session's metadata. If it is fixed (for example always Apple silicon), a constant per backend is enough.
Until then, (1) with the local arch is the practical default.
- proxy: this needs a host-info answer from the agent-device daemon, or a
-
Put the arch in the iOS simulator cache key, mirroring Android's
-arm64-v8a. For example, add-arm64or-x86_64when the build is single-arch, and keep the current key for universal builds. This key is shared with@stim-cli/expo-build-cacheandcache-packages.test.ts, so both follow. Changing the key invalidates existing iOS simulator entries once. A universal artifact could still satisfy either arch as a fallback, but only as a deliberate decision. -
Manual override (proposal only). An
ios.simulatorArchsetting (auto | arm64 | x86_64) inpackages/core/state/settings-registry.tswould cover mismatched local and remote hosts until detection exists. Adding a setting or flag needs a product decision, so it is not part of the fix unless that decision is made.
Verification for a fix:
lipo -archson the--remoteartifact shows one arch.- A real
stim ios --remote proxyinstall and launch still succeeds. - The cache key carries the arch.
- An x86_64 key misses an arm64 entry.
Activity
janicduplessis commentedon Sep 28, 2026
Checked on a real EAS Simulator session (iOS, eas-cli 24.8.0, 2026-09-28, stopped after about 2 minutes):
eas simulator:get --jsonhas no architecture or host field. It only has id, name, status, platform, timestamps, remote config and artifacts./healthreturns only the service name and version.agent-device devices --jsonlists simulators with platform, appleOs, id, name, kind and booted, again with no architecture.So neither backend reports the remote architecture today. Defaulting
--remoteto arm64, or to this Mac's architecture, is right for EAS in practice. Reliable detection would need agent-device to report the host architecture, which is an upstream request.janicduplessis commentedon Sep 28, 2026
Claimed: fix/1803-remote-ios-single-arch
fix(ios): build one simulator arch for --remote and key iOS simulator…