stim 1.25.0 → 1.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/README.md +1 -1
  2. package/dist/{acquire-artifact-zA4S7RHE.mjs → acquire-artifact-BKN2Yuhg.mjs} +2 -2
  3. package/dist/{act-BbHS3oV8.mjs → act-ROviTF_t.mjs} +415 -118
  4. package/dist/{activity-BpjwrQXU.mjs → activity-Cd-ysFes.mjs} +6 -7
  5. package/dist/{agent-device-usage-output-Bnkerfod.mjs → agent-device-usage-output-C5nCKWp9.mjs} +5 -5
  6. package/dist/{android-CNZgRC-4.mjs → android--HBgpvpW.mjs} +5 -5
  7. package/dist/{android-BGwuK3Mc.mjs → android-Cd6w7_qw.mjs} +134 -57
  8. package/dist/{android-saBeB4lr.mjs → android-Deb9X3_Y.mjs} +37 -15
  9. package/dist/{android-Cjcj8MCw.mjs → android-DriNoAVy.mjs} +99 -47
  10. package/dist/{android-cas-BVqh2Zep.mjs → android-cas-D5e6ecUg.mjs} +4 -4
  11. package/dist/android-cas-compiler.mjs +1 -1
  12. package/dist/{android-project-CTYaETc0.d.mts → android-project-DUGMbq28.d.mts} +55 -1
  13. package/dist/api.d.mts +20 -7
  14. package/dist/api.mjs +42 -32
  15. package/dist/apk-signed-tail-BHQHx3Rm.mjs +142 -0
  16. package/dist/{artifact-Co8zixVx.mjs → artifact-0AGJYB9O.mjs} +28 -28
  17. package/dist/{artifact-DN8JNwqS.mjs → artifact-faemIZv5.mjs} +40 -30
  18. package/dist/{artifact-platform-CMRc0M_r.mjs → artifact-platform-BO2Uvk_A.mjs} +1 -1
  19. package/dist/{attempt-DW-mQqM7.mjs → attempt-ClyJlZPT.mjs} +4 -2
  20. package/dist/auto-placement-z-H5pJza.mjs +350 -0
  21. package/dist/{browser-web-CCgZsHmI.mjs → browser-web-slD8AvLp.mjs} +16 -17
  22. package/dist/{build-cache-CKCzxzxA.mjs → build-cache-gBJ-WKzA.mjs} +141 -37
  23. package/dist/{build-CAiw8Ezm.mjs → build-hS0QyFlQ.mjs} +38 -31
  24. package/dist/{build-plan-zCuHGlA8.mjs → build-plan-CBqYbzYM.mjs} +7 -6
  25. package/dist/{cache-manifest-BPXRONJz.mjs → cache-manifest-DEjqGGat.mjs} +1 -1
  26. package/dist/cache-manifest.mjs +1 -1
  27. package/dist/{budget-HyShrIpL.mjs → ccache-BWTze1Ls.mjs} +634 -1514
  28. package/dist/{cdp-BG3FJCCx.mjs → cdp-C3svvXiE.mjs} +1 -1
  29. package/dist/{chrome-BvV8ssI3.mjs → chrome-DyDWurWG.mjs} +1 -1
  30. package/dist/{cli-C4w607Pz.mjs → cli-ftXDLto1.mjs} +24 -23
  31. package/dist/cli.mjs +1 -1
  32. package/dist/{client-BQsmyp_-.mjs → client-xsdxrhWA.mjs} +77 -116
  33. package/dist/collector-run.d.mts +10 -9
  34. package/dist/collector-run.mjs +42 -27
  35. package/dist/{command-output-BBvS1H6v.mjs → command-output-Dj-WT4BP.mjs} +1 -1
  36. package/dist/{config-BZhXY15H.mjs → config-Cn94Jc0g.mjs} +343 -18
  37. package/dist/{launch-DscF1QfM.mjs → dependencies-BGokZ-Ng.mjs} +330 -279
  38. package/dist/{dependency-state-BwNCYG5Q.mjs → dependency-state-C3OtgG94.mjs} +14 -7
  39. package/dist/{detached-entry-KmieOKgN.mjs → detached-entry-BPlSaCwC.mjs} +1 -1
  40. package/dist/{dev-client-CHoXXZeg.mjs → dev-client-BMca82xo.mjs} +2 -2
  41. package/dist/{device-F5-4z9ef.mjs → device-BJm0irrq.mjs} +16 -16
  42. package/dist/{device-capacity-ZYaRALv5.mjs → device-capacity-6LP7QsaA.mjs} +103 -25
  43. package/dist/device-host-worker.mjs +19 -21
  44. package/dist/{device-ios-DkHBrmvH.mjs → device-ios-534c14fz.mjs} +288 -28
  45. package/dist/{device-lease-run-ogEeJ62K.mjs → device-lease-run-DwsGfjUN.mjs} +5 -5
  46. package/dist/{device-pool-BIg8MU8l.mjs → device-pool-BnQqvs6o.mjs} +3 -3
  47. package/dist/{device-B9FqTBYW.mjs → device-tM41-TUq.mjs} +25 -24
  48. package/dist/{doctor-A_Nr_fDe.mjs → doctor-Dbx1ZpD2.mjs} +1 -1
  49. package/dist/{doctor-k3P3PizH.mjs → doctor-DyqE9n1J.mjs} +28 -16
  50. package/dist/{doctor-Bm8TVpGn.mjs → doctor-F-3VUUyN.mjs} +128 -35
  51. package/dist/{doctor-fingerprint-zlujA1Gs.mjs → doctor-fingerprint-o7ZI9YLn.mjs} +9 -3
  52. package/dist/doctor-fingerprint.mjs +1 -1
  53. package/dist/{doctor-watchman-BovI1y3-.mjs → doctor-watchman-CxDbyrrt.mjs} +5 -5
  54. package/dist/{eas-build-CyDKNTBP.mjs → eas-build-CjNSWmjo.mjs} +4 -4
  55. package/dist/eas-fallback-9fqb46kW.mjs +87 -0
  56. package/dist/{eas-session-ledger-BMCA1gdO.mjs → eas-session-ledger-DrP4OuuM.mjs} +6 -6
  57. package/dist/{error-diagnostics-B1Mj_-7h.mjs → error-diagnostics-snej9k4j.mjs} +51 -25
  58. package/dist/{exec-VFRpu1iA.mjs → exec-aULwtNly.mjs} +57 -6
  59. package/dist/{gc-eaz403W6.mjs → gc-BRwAY3qT.mjs} +2 -2
  60. package/dist/{gc-B9jcnmIk.mjs → gc-CGZ1Yg88.mjs} +218 -72
  61. package/dist/{guide-DR0K0xW9.mjs → guide-_rfEh_zM.mjs} +839 -222
  62. package/dist/{hosted-android-Bvb2-ZxD.mjs → hosted-android-BMKzUNnY.mjs} +4 -4
  63. package/dist/{hosted-client-rsRqysJc.mjs → hosted-client-D7Pk_Oo1.mjs} +13 -9
  64. package/dist/{hosted-logs-CWKGYWNX.mjs → hosted-logs-B1gqQuva.mjs} +18 -12
  65. package/dist/hosted-logs-sync-Dg3qrZmV.mjs +195 -0
  66. package/dist/{hosted-macos-d0pv9Tth.mjs → hosted-macos-B9w4H8hc.mjs} +6 -6
  67. package/dist/{hosted-native-BxTizGU2.mjs → hosted-native-CdgaiuyP.mjs} +17 -17
  68. package/dist/{idle-shutdown-BIyfl0Zj.mjs → idle-shutdown-Cbvo5KEh.mjs} +21 -9
  69. package/dist/{ios-BRVM8b0P.mjs → ios-B5HlVMhQ.mjs} +138 -55
  70. package/dist/{ios-Dvt97HgQ.mjs → ios-Bk4mWbtS.mjs} +8 -8
  71. package/dist/{ios-ENC4sRPA.mjs → ios-CsRVik64.mjs} +7 -1
  72. package/dist/{ios-BQzvJlzw.mjs → ios-E26qSpkb.mjs} +94 -11
  73. package/dist/{ios-device-CKSvk0bu.mjs → ios-device-C_QkIf1Z.mjs} +3 -3
  74. package/dist/{ios-device-B6HK951C.mjs → ios-device-CiBA-er9.mjs} +1 -1
  75. package/dist/{ios-project-DCUDo06I.mjs → ios-project-D8Jcy_br.mjs} +22 -9
  76. package/dist/{ios-signing-4c_qXvQm.mjs → ios-signing-BJzvDPfU.mjs} +2 -2
  77. package/dist/{ios-state-CT9nV5SP.mjs → ios-state-BZ03NFgF.mjs} +1 -1
  78. package/dist/{js-swap-Z9HbC4cj.mjs → js-swap-Dg2a945i.mjs} +8 -7
  79. package/dist/{launch-D0lPvFFu.mjs → launch-CRyBeNsA.mjs} +53 -44
  80. package/dist/lightened-BCsdAGj_.mjs +171 -0
  81. package/dist/{logs-BsIRre8x.mjs → logs-CC_-ZzEE.mjs} +10 -174
  82. package/dist/{logs-Crs9y34Z.mjs → logs-aJQb-RDg.mjs} +112 -38
  83. package/dist/{macos-CcUlJPMu.mjs → macos-BGmTpnv9.mjs} +1 -1
  84. package/dist/{macos-D477nfzH.mjs → macos-DlCBhMw5.mjs} +73 -39
  85. package/dist/macos-run.mjs +1 -1
  86. package/dist/maintenance-run.mjs +30 -13
  87. package/dist/{metro-1pJgCX4U.mjs → metro-CvyD-xwe.mjs} +4 -4
  88. package/dist/{metro-gateway-Bp9gJQAN.mjs → metro-gateway-BnHdqMO_.mjs} +3 -4
  89. package/dist/{named-ports-BVbn8SvC.mjs → named-ports-BCyMWA6b.mjs} +3 -3
  90. package/dist/{native-android-BvMizJn1.mjs → native-android-DlpxCyOP.mjs} +17 -18
  91. package/dist/{native-gradle-artifact-inputs-BJscK4vN.mjs → native-gradle-artifact-inputs-C13qT7R_.mjs} +5 -5
  92. package/dist/{native-runtime-B9w_Qyz3.mjs → native-runtime-DqaIBO5w.mjs} +9 -9
  93. package/dist/{native-source-D-4Dh4JU.mjs → native-source-D1AzWiuP.mjs} +1 -1
  94. package/dist/{native-xcode-inputs-Cv4l-obn.mjs → native-xcode-inputs-B1W8SrxB.mjs} +3 -3
  95. package/dist/{native-xcode-ios-DGrg90hE.mjs → native-xcode-ios-QPP0Rm68.mjs} +17 -18
  96. package/dist/{ndjson-CgE_o3Dl.mjs → ndjson-Dy2W4vRa.mjs} +2 -2
  97. package/dist/offload-worker.d.mts +3 -1
  98. package/dist/offload-worker.mjs +34 -25
  99. package/dist/{operation-exec-CUZifp6Z.mjs → operation-exec-Dp4FuIGR.mjs} +1 -1
  100. package/dist/{ownership-jxxz-19m.mjs → ownership-Bx-0cku-.mjs} +3 -3
  101. package/dist/{page-CzEA_-5_.mjs → page-CQJlcj3x.mjs} +2 -2
  102. package/dist/{phone-reverses-CYb7RRAx.mjs → phone-reverses-C9yAaKEv.mjs} +18 -4
  103. package/dist/{placement-log-DjOXJRIH.mjs → placement-log-BXUbiZM6.mjs} +3 -3
  104. package/dist/{plan-placement-BqKgKHCi.mjs → plan-placement-UxnQjv5Z.mjs} +36 -10
  105. package/dist/{ports-DtS1Amb0.mjs → ports-uUrghqWN.mjs} +3 -3
  106. package/dist/prebuild-BDpToDgR.mjs +1246 -0
  107. package/dist/{project-BiC68mIp.mjs → project-DYj-HVx6.mjs} +17 -18
  108. package/dist/{pull-request-CbNlkFok.mjs → pull-request-BzLmzAIQ.mjs} +2 -2
  109. package/dist/pull-requests.mjs +1 -1
  110. package/dist/{react-native-android-DRYqoJc4.mjs → react-native-android-DDBccgJw.mjs} +56 -36
  111. package/dist/{react-native-build-CwZB-agu.mjs → react-native-build-DaK2IFyP.mjs} +9 -9
  112. package/dist/{react-native-doctor-BCrytK7h.mjs → react-native-doctor-B-Mw6DcR.mjs} +50 -9
  113. package/dist/{react-native-ios-Cr1buNFo.mjs → react-native-ios-DjYwUF9U.mjs} +45 -25
  114. package/dist/{recordings-BaoQ0t-J.mjs → recordings-C20iUZfp.mjs} +1 -1
  115. package/dist/{registration-CN6nlhcD.mjs → registration-BwCe64PH.mjs} +1 -1
  116. package/dist/{reload-Beftw2X3.mjs → reload-DJBeF_rC.mjs} +1 -1
  117. package/dist/{reload-FbSgMF8x.mjs → reload-fUdkH2_p.mjs} +16 -16
  118. package/dist/{remote-BuZj6AyU.mjs → remote-y6n99WMW.mjs} +22 -9
  119. package/dist/{result-CR2vk2cj.mjs → result-CTqBw2Te.mjs} +32 -21
  120. package/dist/{run-BtzE09Dl.mjs → run-DaCeNKRm.mjs} +3 -3
  121. package/dist/{selection-D5cxEl3t.mjs → selection-Bs8phwhN.mjs} +183 -62
  122. package/dist/{server-bare-CH9X0RLn.mjs → server-bare-BOh-iZEj.mjs} +2 -2
  123. package/dist/{server-command-CaBbDNU2.mjs → server-command-8CuA51Cv.mjs} +6 -6
  124. package/dist/{server-expo-Ck8EIidM.mjs → server-expo-ABQs2XFA.mjs} +4 -4
  125. package/dist/server-expo-Ba_Y21vB.mjs +2 -0
  126. package/dist/{settings-B5tCoq9N.mjs → settings-BDVI8Ivc.mjs} +331 -62
  127. package/dist/{settings-DzH9mGQZ.mjs → settings-CMHhQNwI.mjs} +24 -3
  128. package/dist/settings-payload-cm60Zhib.mjs +257 -0
  129. package/dist/settings-write-BdNRLlBn.mjs +185 -0
  130. package/dist/settings.schema.json +84 -39
  131. package/dist/{simslim-BjxszUAb.mjs → simslim-DCQwzoPy.mjs} +4 -3
  132. package/dist/{slot-launch-QeuJMlnJ.mjs → slot-launch-0l7RqSpr.mjs} +6 -6
  133. package/dist/{stage-D8xlRU2D.mjs → stage-F4cAfJGi.mjs} +2 -2
  134. package/dist/{start-C1JLjkNw.mjs → start-DcqnF9p8.mjs} +1 -1
  135. package/dist/{start-YMpNwLhh.mjs → start-DrPwER_2.mjs} +27 -23
  136. package/dist/{state-DMRNP7-4.mjs → state-BX1y5syV.mjs} +4 -5
  137. package/dist/{state-DPaM3u5K.mjs → state-CZxLyetD.mjs} +2 -3
  138. package/dist/{stats-CP7STzZw.mjs → stats-GxuCbles.mjs} +5 -5
  139. package/dist/{stats-report-n2U5plhk.mjs → stats-report-B86Cv7Uc.mjs} +1 -1
  140. package/dist/{status-Fect2w9R.mjs → status-7ULDEJ1e.mjs} +1 -1
  141. package/dist/{status-CRLiJ-O_.mjs → status-BfnK9pUd.mjs} +25 -26
  142. package/dist/{status-cNw6i0_t.mjs → status-Bj7ZYvvc.mjs} +1 -1
  143. package/dist/{status-watch-Bc2J4TZn.mjs → status-watch-BfabQn03.mjs} +5 -5
  144. package/dist/status-worker.mjs +1 -1
  145. package/dist/{stim-desktop-DS0mzR8U.mjs → stim-desktop-CbWo21kb.mjs} +2 -2
  146. package/dist/{stop-COP6PQqw.mjs → stop-CH79S3IZ.mjs} +5 -6
  147. package/dist/{stop-Db9Kh-UA.mjs → stop-D-EAr25B.mjs} +25 -22
  148. package/dist/{stop-Br7LKxBE.mjs → stop-x1Yu3He4.mjs} +2 -2
  149. package/dist/supervisor-run.d.mts +1 -0
  150. package/dist/supervisor-run.mjs +17 -18
  151. package/dist/{support-qKcbgmI5.mjs → support-Be0UeVZU.mjs} +10 -10
  152. package/dist/{support-Cxj9CmII.mjs → support-CNg_YsOk.mjs} +6 -6
  153. package/dist/{swiftpm-artifact-inputs-iY9fsmOc.mjs → swiftpm-artifact-inputs-uPqYvGBv.mjs} +4 -4
  154. package/dist/{swiftpm-macos-Bp_sn9P4.mjs → swiftpm-macos-Ch4YjY_A.mjs} +11 -12
  155. package/dist/toolchain-CbCG6C5Z.mjs +2413 -0
  156. package/dist/toolchain-JYAOquPg.mjs +2 -0
  157. package/dist/{trigger-CBMe2YLH.mjs → trigger-DMou-X7j.mjs} +3 -3
  158. package/dist/trigger-qp2ji5wE.mjs +2 -0
  159. package/dist/{warm-progress-BV3ZPmcY.mjs → warm-progress-C0RsbngN.mjs} +731 -728
  160. package/dist/{web-B4Yjyt1G.mjs → web-Cq_AQ4DK.mjs} +9 -10
  161. package/dist/web-Je6abcpz.mjs +2 -0
  162. package/dist/web-run.mjs +9 -9
  163. package/dist/windows-report.mjs +1 -1
  164. package/dist/{workspace-state-CPSp3mav.mjs → workspace-state-BjqdjnJR.mjs} +46 -8
  165. package/dist/{workspaces-C9w4aeXF.mjs → workspaces-DFbj07jp.mjs} +1801 -76
  166. package/dist/{worktree-CclFIe1Z.mjs → worktree-COAJ3-OD.mjs} +934 -511
  167. package/dist/{worktree-Ct9Gk3KL.mjs → worktree-JGUd_53L.mjs} +1 -1
  168. package/package.json +8 -8
  169. package/dist/ccache-h1FKhZeg.mjs +0 -143
  170. package/dist/created-devices-D2Aca-xu.mjs +0 -52
  171. package/dist/eas-fallback-gaZi-dJL.mjs +0 -381
  172. package/dist/hosted-ios-RtVcJH3u.mjs +0 -15
  173. package/dist/native-run-D5OJsGwA.mjs +0 -276
  174. package/dist/paths-DQtTgsBf.mjs +0 -42
  175. package/dist/prebuild-JUaqOLpc.mjs +0 -501
  176. package/dist/server-expo-C16Zf2k7.mjs +0 -2
  177. package/dist/settings-write-n0zwfohx.mjs +0 -360
  178. package/dist/toolchain-DB1aeqxO.mjs +0 -2
  179. package/dist/toolchain-VzLJzZ7O.mjs +0 -908
  180. package/dist/trigger-BLsHgAGR.mjs +0 -2
  181. package/dist/web-DS4UDWvf.mjs +0 -2
  182. package/dist/xcode-CiIIDnuD.mjs +0 -910
@@ -1,8 +1,8 @@
1
- import { t as ANDROID_AVD_CONFIG_HELP } from "./settings-B5tCoq9N.mjs";
2
- import { i as findProjectRoot$1 } from "./project-BiC68mIp.mjs";
3
- import { h as RECENT_LAUNCH_MS } from "./eas-session-ledger-BMCA1gdO.mjs";
4
- import { o as guideStatus, t as WATCHMAN_NESTED_WORKTREES } from "./doctor-watchman-BovI1y3-.mjs";
5
- import "./status-watch-Bc2J4TZn.mjs";
1
+ import { t as ANDROID_AVD_CONFIG_HELP } from "./settings-BDVI8Ivc.mjs";
2
+ import { i as findProjectRoot$1 } from "./project-DYj-HVx6.mjs";
3
+ import { h as RECENT_LAUNCH_MS } from "./eas-session-ledger-DrP4OuuM.mjs";
4
+ import { o as guideStatus, t as WATCHMAN_NESTED_WORKTREES } from "./doctor-watchman-CxDbyrrt.mjs";
5
+ import "./status-watch-BfabQn03.mjs";
6
6
  import { SETTINGS, SETTINGS_SCHEMA_URL, TUTORIAL_VERSION } from "@stim-cli/core/state";
7
7
  import chalk from "chalk";
8
8
  //#region src/guide/agent.ts
@@ -188,6 +188,11 @@ RULES DURING THE LOOP
188
188
  you do other work.
189
189
  - Run ios or android again after a native input changes. A JavaScript-only
190
190
  change does not need one.
191
+ - The build cache counts an installed package by its name and version, so it
192
+ cannot see a file you edit inside node_modules/. Change a dependency's native
193
+ code with a patch (patch-package or pnpm patch), which is hashed, or run ios or
194
+ android with --no-build-cache --remote-build local to compile it here. See
195
+ guide lifecycle builds.
191
196
  - For stale Metro transforms or file-map state, use stim start --reset-cache.
192
197
  It restarts only this app's verified owned Metro, preserving devices and other
193
198
  apps' caches. See guide lifecycle for reset scope.
@@ -292,16 +297,20 @@ reserved named ports, even outside the workspace. Read guide ports before use.
292
297
 
293
298
  Ask the user before these actions:
294
299
 
295
- - worktree remove, because it deletes the worktree and gives up its owned
296
- device. It works with any linked worktree, warmed or not, without requiring
300
+ - worktree remove of a worktree you did not create, because it deletes the
301
+ worktree and gives up its owned device. It works with any linked worktree, warmed or not, without requiring
297
302
  a Stim registry entry. Git-created branches are kept; a branch with an
298
303
  existing Stim ownership record is deleted only when it has no unique commits.
299
304
  - worktree remove --force, because it also discards uncommitted and untracked
300
305
  files.
301
306
  - gc --delete, because it deletes orphaned resources, removes clean linked
302
307
  worktrees whose branch or pull request is merged (or whose pull request
303
- was closed), and clears the build outputs of every
304
- workspace not in use. Run stim gc --json first and show
308
+ was closed), and lightens every workspace that is not building and ran
309
+ no ios, android or macos command in the last 15 minutes, a running dev
310
+ server included, by clearing its build outputs and its git-ignored
311
+ in-tree build directories, and deletes node_modules and ios/Pods of idle
312
+ linked worktrees, which the next start, ios or android restores with
313
+ worktree warm. Run stim gc --json first and show
305
314
  the user the entries its sections list (guide facts gc). gc --delete
306
315
  --cache all empties the shared build caches and those outputs instead, and
307
316
  gc --delete --cache workspaces clears only the outputs; both inspect
@@ -322,8 +331,10 @@ Ask the user before these actions:
322
331
 
323
332
  LEAVE THE MACHINE CLEAN
324
333
 
325
- Run stim stop when you pause or finish a task, and stim worktree remove once
326
- it is merged or abandoned. Never create a simulator or emulator by hand;
334
+ Run stim stop when you pause or finish a task, and stim worktree remove on
335
+ the worktree you created once its work is merged or abandoned. Keep scratch
336
+ files, test output and downloads outside the worktree: a stray untracked file
337
+ blocks that removal, and automatic cleanup then has to archive it. Never create a simulator or emulator by hand;
327
338
  every device comes from Stim. Before starting a new workspace, check stim
328
339
  status for an idle workspace to reuse and confirm capacity headroom instead of
329
340
  adding another. Never quit Device Hub or Simulator.app: on Xcode 27
@@ -395,7 +406,7 @@ FULL TOPIC LIST
395
406
  stim guide errors fallbacks # swap, cache, and install notes on a release cache hit
396
407
  stim guide lifecycle # the ordered flow, consent rules, and capacity
397
408
  stim guide lifecycle verification # reproduce, edit, verify the UI, and retain proof
398
- stim guide lifecycle readiness # add optional app readiness logs; no package required
409
+ stim guide lifecycle readiness # add recommended app readiness logs; no package required
399
410
  stim guide lifecycle builds # build optimizations, optional cache warm-up, fingerprints
400
411
  stim guide lifecycle concurrency # shared builds, wait timeouts, capacity limits
401
412
  stim guide lifecycle budget # disk and memory budgets, automatic reclaim
@@ -1009,7 +1020,7 @@ leased until <time>" for each one.`,
1009
1020
  JavaScript running -- the first screen may
1010
1021
  still be rendering\` (\`stable for 3s\` when
1011
1022
  no \`Running\` log arrived). Poll the UI
1012
- before you trust a screenshot. Optional
1023
+ before you trust a screenshot. Recommended
1013
1024
  app-declared readiness adds a separate
1014
1025
  stderr readiness phase; it does not change
1015
1026
  this field. See
@@ -1256,13 +1267,27 @@ leased until <time>" for each one.`,
1256
1267
  pins Node (bin, version and match are null when no install
1257
1268
  fits the pin; match is "exact", or "major" for the newest
1258
1269
  install of the pinned major not older than the pin), else null
1259
- remoteMachines one { machine, state, dnsName?, deviceId?, requestedAt?,
1270
+ remoteMachines one { machine, state, diagnosis?, dnsName?, deviceId?, requestedAt?,
1260
1271
  expiresAt?, offloadable?, reasons?, problems?, capacity?, host? } per
1261
1272
  remote.machines entry, for its build approval; state is "approved", "pending", "not-asked",
1262
1273
  "revoked" (revoked or denied), "lapsed" (the request lapsed
1263
1274
  before approval), "key-changed", "outdated",
1264
1275
  "not-on-tailnet", "tailscale-off", "unreachable" or
1265
- "invalid". host has the same shape and presence rule as in
1276
+ "invalid". diagnosis is { layer, code, title, message, remedy }
1277
+ for the first layer that stopped this machine, present for
1278
+ "tailscale-off", "not-on-tailnet", "outdated" and "unreachable"
1279
+ (deviceHosts carries it too); the codes are
1280
+ tailscale-stopped, tailscale-logged-out, tailscale-unavailable
1281
+ (this machine's Tailscale), peer-not-found, peer-ambiguous,
1282
+ peer-offline (with last seen), peer-no-answer (the connection
1283
+ got no reply and tailscale ping fails), route-missing (nothing accepts
1284
+ connections on the serve port), route-tls, route-http-error
1285
+ (any HTTP status but 502 and 503), server-not-running (502:
1286
+ nothing behind the route), server-not-ready (503: running but
1287
+ not serving; its message gives the cause the server reports),
1288
+ server-no-answer (silent route while tailscale ping reaches the Mac), server-outdated and connect-failed (none of the above).
1289
+ Tailscale's own "offline" flag lags a reachable peer, so an
1290
+ answer from the machine outranks it. host has the same shape and presence rule as in
1266
1291
  deviceHosts, as do expiresAt (a pending request's lapse
1267
1292
  time) and "lapsed" (--fix asks again). An approved machine also carries offloadable,
1268
1293
  true when it would take this app's builds now (iOS
@@ -1278,7 +1303,16 @@ leased until <time>" for each one.`,
1278
1303
  checkout, stim-build, arch, xcode, simulator-sdk, macos-sdk,
1279
1304
  cocoapods, bundler, runtime, jdk, android-sdk, ndk,
1280
1305
  build-tools, compile-sdk, disk, busy or platform). problems lists the same
1281
- reasons as { code, reason } with that code. capacity
1306
+ reasons as { code, reason } with that code; an ndk, build-tools,
1307
+ compile-sdk, jdk, xcode or runtime problem can also carry commands and a
1308
+ lead, the lines to run on that machine (sdkmanager, the JDK install,
1309
+ xcode-select, xcodebuild or xcodes) and what they do, or followsFrom, the
1310
+ code of the problem whose fix also clears it. notes lists what a build there does without stopping:
1311
+ { code, reason, commands, lead } for an ndk, build-tools or compile-sdk package its
1312
+ Android SDK lacks and Gradle downloads on the first build (licenses
1313
+ accepted there, no android.builder.sdkDownload=false in the app), with the sdkmanager
1314
+ command that installs it ahead of time; each
1315
+ also a build-machine-<code> finding at level note. capacity
1282
1316
  is the machine's offer: { running, max, diskFreeBytes,
1283
1317
  minDiskFreeBytes, cpus?, loadPerCore?, builds?, maxBuilds?,
1284
1318
  maxLoadPerCore?, memoryUsedBytes?, memoryTotalBytes?, declined? }; an older stim-server omits the
@@ -1289,6 +1323,7 @@ leased until <time>" for each one.`,
1289
1323
  with an abnormally large footprint and, when it is safe to
1290
1324
  restart, the command
1291
1325
  offload-candidate is a note after 3+ successful local cold builds in 7 days average over 3 min, with no remote.machines and an online tailnet Mac; open Stim Desktop Settings > Remote Machines > Add
1326
+ readiness-markers-missing is a note when no JS or TS source under the app directory contains [stim:readiness]; the scan skips node_modules, native and build output directories and dot-directories, and reports nothing past 5000 source files; see stim guide lifecycle readiness
1292
1327
 
1293
1328
  ON FAILURE
1294
1329
  \`start\`, \`ios\`, \`android\` and \`web\` all print the error contract instead,
@@ -1306,7 +1341,8 @@ ON FAILURE
1306
1341
  An \`ios\` or \`android\` run that \`stim stop\` or Ctrl-C interrupted
1307
1342
  reports STIM_CANCELLED and exits 130. A
1308
1343
  run that exits on the interrupt at once (a second Ctrl-C, or Ctrl-C with no
1309
- build tool or SimSlim to stop) exits 130 with no payload.
1344
+ build tool, SimSlim, launch readiness wait or hosted app delivery to stop)
1345
+ exits 130 with no payload.
1310
1346
 
1311
1347
  \`stop --json\` prints { root, ok, supervisor, collectors, metro, device,
1312
1348
  port, metroTunnel, releasedLeases, macos? }. macos is { status: stopped | failed,
@@ -1503,7 +1539,8 @@ RULES
1503
1539
  why) or "failed" (detail says why and what to retry).
1504
1540
  kind: device, parkedDevice, parkedHostedDevice, idleDevice,
1505
1541
  deviceRecord,
1506
- workspaceOutputs, recording, workspaceDirectory, project,
1542
+ workspaceOutputs, worktreeDependencies, recording,
1543
+ workspaceDirectory, project,
1507
1544
  buildLock, buildSlot, deviceLease, easSession, worktree,
1508
1545
  cache, gradleVersion, tempLeftover (one "done" labelled
1509
1546
  with the TMPDIR it cleared, plus a "kept" or "failed" per
@@ -1571,9 +1608,9 @@ RULES
1571
1608
  else null. buildState is { bytes, dirs:
1572
1609
  { path, bytes } } for the in-tree build and
1573
1610
  dependency directories automatic maintenance
1574
- clears (ios/build, android/app/build,
1575
- android/build, android/.gradle, .build,
1576
- node_modules, ios/Pods, at the root, a
1611
+ clears (android/app/build, android/build,
1612
+ android/.gradle, .build, node_modules,
1613
+ ios/Pods, at the root, a
1577
1614
  project root, or apps/* and packages/* for
1578
1615
  build directories) and a root .pnpm-store,
1579
1616
  paths relative to the worktree, largest
@@ -1672,18 +1709,29 @@ RULES
1672
1709
  Metro, client and device logs over twice the
1673
1710
  8 MiB cap; willTrim marks the ones it would
1674
1711
  trim
1712
+ worktreeDependencies { path, willDelete, detail } node_modules and
1713
+ ios/Pods of linked worktrees; willDelete marks
1714
+ the ones --delete would lighten, and detail
1715
+ says why the others stay. Sizes are not
1716
+ measured: APFS clones make them misleading
1675
1717
  recordings { dir, projectRoot, bytes, deleteBytes,
1676
1718
  willDelete, withWorkspace, reason, detail }
1677
1719
  recordings/ of each workspace; deleteBytes is
1678
1720
  what --delete would remove; withWorkspace
1679
1721
  marks those of a gone workspace, removed with
1680
1722
  its workspace directory
1681
- workspaceBuildOutputs { dir, projectRoot, bytes, idleDays, willClear,
1682
- reason, detail } derived-data, gradle-build,
1683
- android-cas, cache-provider, macos/build,
1684
- macos/<Product>.app and macos/staging-* of each
1685
- workspace; willClear marks the ones --delete
1686
- would clear; --cache workspaces omits the
1723
+ workspaceBuildOutputs { dir, projectRoot, inTreeDirs, bytes, idleDays,
1724
+ willClear, reason, detail } derived-data,
1725
+ gradle-build, android-cas, cache-provider,
1726
+ macos/build, macos/<Product>.app and
1727
+ macos/staging-* of each workspace; inTreeDirs
1728
+ lists the project's android/app/build,
1729
+ android/build,
1730
+ android/.gradle and .build that --delete
1731
+ clears with them, every file in them
1732
+ ignored by git and none tracked, and
1733
+ bytes includes them; willClear marks the ones
1734
+ --delete would lighten; --cache workspaces omits the
1687
1735
  directories that --delete removes whole (dead
1688
1736
  projects and orphanedWorkspaces), and --cache
1689
1737
  all does not
@@ -1742,11 +1790,13 @@ RULES
1742
1790
  bytes, willDelete, kept }] } | null ignored
1743
1791
  directories worktree remove moved out of a
1744
1792
  worktree into $STIM_HOME/trash before git
1745
- removed it. bytes is null while a removal is
1793
+ removed it, and paths worktree warm moved out
1794
+ of a copy. bytes is null while a removal is
1746
1795
  deleting the entry. kept names the live or
1747
1796
  unresolved ownership claim, the missing or
1748
1797
  invalid trash.json, or the worktree that
1749
- still exists, that keeps a directory;
1798
+ still exists (not for a warm entry), that
1799
+ keeps a directory;
1750
1800
  --delete deletes the rest, only remove-*
1751
1801
  entries with a valid trash.json. null when the
1752
1802
  trash is empty or under any --cache scope
@@ -2244,6 +2294,10 @@ RULES
2244
2294
  build: ios compile on janics-mac-mini (build, 2m10s), 3m05s elapsed
2245
2295
  build: ios launch (bundling JS 45%, 12s), 58s elapsed
2246
2296
 
2297
+ The machine keeps every native build run, not only the latest, in
2298
+ $STIM_HOME/builds/builds.ndjson (newest 500), read with
2299
+ \`stim logs --source builds --json\` (\`guide logs\`, BUILD LEDGER).
2300
+
2247
2301
  An environment with a recorded run also carries lastBuilds, each
2248
2302
  platform's most recent ios or android run, finished or failed:
2249
2303
 
@@ -2440,6 +2494,8 @@ RULES
2440
2494
  lastPass carries startedAt, durationMs, trigger, mode, freedBytes
2441
2495
  (0 in report mode), actions, stopped (0), blocked. recent holds the last 20 action, failure
2442
2496
  and blocked NDJSON records from maintenance/maintenance.ndjson.
2497
+ What each run cleared, kept or failed is in $STIM_HOME/gc/gc.ndjson,
2498
+ read with \`stim logs --source gc --json\` (\`guide logs\`, GC LOG).
2443
2499
  invalid names invalid settings; invalid cache caps use their defaults,
2444
2500
  while invalid maintenance settings disable passes. claim contains an
2445
2501
  unresolved claim's path and reason, plus removeCommand from the claim
@@ -2519,9 +2575,10 @@ RULES
2519
2575
  plan does not, so changes compare the fingerprint before that
2520
2576
  prebuild; changeCount 0 then means those inputs match the
2521
2577
  baseline. rekeyedBy is empty.
2522
- placement with ios.remote or android.remote set to auto or a Mac,
2523
- where the plan assumes the device runs: "on <machine>" or
2524
- "this machine; auto may use <machines>"; absent otherwise
2578
+ placement with ios.remote or android.remote set to auto, remote,
2579
+ remote-only or a Mac, where the plan assumes the device runs:
2580
+ "on <machine>" or "this machine; <word> may use <machines>";
2581
+ absent otherwise
2525
2582
  refusal { code, message, remedy } when the run would refuse:
2526
2583
  STIM_PREBUILD_FAILED for a tracked native dir the fingerprint
2527
2584
  leaves out, STIM_EAS_BUILD_MISSING for an EAS miss
@@ -2676,9 +2733,9 @@ BUILD PLACEMENT (\`offload\`)
2676
2733
  decision is "here", "offloaded", or "fell-back" (it tried a remote machine
2677
2734
  and built here). reason is why: the run's \`placement:\` reason, such as
2678
2735
  "load 0.6/core, 1 of 3 build slots busy here" (auto keeps the build here
2679
- while this machine has room) or "remote.buildMode is off" (which prints no line),
2736
+ while this machine has room) or "--remote-build local" (which prints no line),
2680
2737
  or for a fallback the cause, prefixed by the machine it concerns, such as
2681
- "mini: no less loaded (load 1.2/core there, 0.6/core here)". buildMs is the compile here, or the offloaded build's
2738
+ "mini: saturated: load 2.4/core there, at or above 2/core". buildMs is the compile here, or the offloaded build's
2682
2739
  total (offer, sync, build and fetch); localEstimateMs is this project's
2683
2740
  last cold build here before the run. A run that hits the cache or waits
2684
2741
  for another workspace's build compiles nothing and records none.
@@ -3168,6 +3225,8 @@ FLAGS
3168
3225
  shared untagged Metro and app client records (not the web
3169
3226
  page's); default also includes untagged legacy records
3170
3227
  --source <s...> metro, client, device, build, agent, maintenance, placement (one or more), or all.
3228
+ gc alone reads the machine-wide gc log (GC LOG below);
3229
+ builds alone reads the machine-wide build ledger (BUILD LEDGER below).
3171
3230
  An unknown value is REJECTED rather than quietly matching
3172
3231
  nothing.
3173
3232
  --level <l> minimum level: debug, info, warn, error, fatal
@@ -3220,7 +3279,8 @@ serve bounded log queries.
3220
3279
  SecTrust, WebKit, CoreUI) that have nothing to do with your app. The proven
3221
3280
  ones are demoted to info by the collector, and iOS main-thread
3222
3281
  "Synchronous URL loading" performance diagnostics (Expo icons served by
3223
- Metro raise them) to warn, so a launch does not count them as app errors;
3282
+ Metro raise them) and SwiftUI "Invalid Configuration" faults (the Expo dev
3283
+ launcher raises them for a duplicated server entry) to warn, so a launch does not count them as app errors;
3224
3284
  the scope rule covers the rest.
3225
3285
  The metro stream carries exactly one demotion of its own, and it is Stim's
3226
3286
  doing: the dev-client deep link \`ios\`/\`android\` open to wire the app to
@@ -3338,12 +3398,99 @@ THE RECORD
3338
3398
  there are none. Stim adds it at query time; it is not in the
3339
3399
  log files.
3340
3400
 
3401
+ IOS BOOT RECORDS
3402
+ Booting an owned iOS simulator writes records to the workspace's device.ndjson:
3403
+ src device, platform ios, deviceId the simulator udid, slot when it is not
3404
+ default, level info unless noted. They show in a plain \`logs\` and in
3405
+ \`--source device\`, and never in \`--errors\`.
3406
+ event ios_boot, with phase:
3407
+ boot_requested just before simctl boot; boot is adopted (a parked
3408
+ simulator), created, existing (an owned simulator
3409
+ that was shut down) or restart
3410
+ bootstatus_done simctl bootstatus -b returned
3411
+ springboard_started SpringBoard finished starting; pid is its pid
3412
+ viewer_opened the simulator viewer was asked to open it
3413
+ simslim_reboot SimSlim changed the simulator and SpringBoard's pid
3414
+ changed; pidBefore and pidAfter are null when the pid
3415
+ could not be read
3416
+ restart warn: SpringBoard is not running after boot and the
3417
+ simulator was shut down; Stim boots it once more
3418
+ event springboard_crash_reports, written before that restart and again if
3419
+ SpringBoard is still missing after it. files lists the SpringBoard-*.ips
3420
+ reports that name this simulator and were captured in the last ten minutes,
3421
+ copied from ~/Library/Logs/DiagnosticReports and its Retired folder into
3422
+ the workspace logs directory; level is warn, or info with an empty files
3423
+ when none matched. dropped counts matching reports that exceeded the limits
3424
+ below and were not copied; failure is the message of a write that failed,
3425
+ with the reports copied before it in files. macOS deletes the originals
3426
+ after about two days; Stim only reads that folder. It looks at the newest
3427
+ 100 reports of at most 1 MiB each, copies at most 8 MiB per run, and keeps
3428
+ at most 40 in the directory.
3429
+
3341
3430
  Maintenance records use src: maintenance and appear in the plain timeline.
3342
3431
  stim logs --source maintenance
3343
3432
  Add --errors to --source maintenance to show only maintenance_failure events;
3344
3433
  failures before a later launch marker are hidden. Machine passes are reported in
3345
3434
  status and maintenance/maintenance.ndjson; this command reads workspace logs.
3346
3435
 
3436
+ GC LOG
3437
+ stim logs --source gc reads $STIM_HOME/gc/gc.ndjson from any directory: one
3438
+ record per item that a gc run, a maintenance action or the budget gate
3439
+ cleared, kept or failed to clear. It cannot be combined with other sources,
3440
+ and --source all does not include it. --errors shows the failed items.
3441
+ Each record has src: gc, level (info, or error for a failed item), msg and
3442
+ runId the stim invocation, or the pass id of maintenance.ndjson
3443
+ trigger manual (stim gc in a terminal), desktop (STIM_DESKTOP_APP is set, so also a gc run through a stim-server Desktop started),
3444
+ maintenance, or budget (the disk, memory or workspace check before
3445
+ start, ios and android)
3446
+ via maintenance only: the command whose run started the pass
3447
+ options gc only: delete, worktrees, olderThan, cache, idleMs
3448
+ kind what the item was (the kind of gc --json results, plus devServer
3449
+ for a dev server the budget gate stops; maintenance also writes
3450
+ buildDirectory and dependencies)
3451
+ status done, kept or failed
3452
+ label, id, path, bytes, detail
3453
+ name, identifier, absolute path when the item is one, bytes freed,
3454
+ and the reason a kept or failed item was left
3455
+ Records of one run share runId. See stim guide cleanup, WHAT WAS CLEARED.
3456
+
3457
+ BUILD LEDGER
3458
+ stim logs --source builds reads $STIM_HOME/builds/builds.ndjson from any
3459
+ directory: one record per native build run (ios, android or macos; compiled
3460
+ here, offloaded or fetched from a cache; succeeded, failed or cancelled). It
3461
+ keeps the newest 500 records, outlives the workspace that ran the build, and
3462
+ is never removed by gc. It cannot be combined with other sources, and --source
3463
+ all does not include it. --errors shows the failed runs. A record has
3464
+ src: builds, event: build, level (info, warn for cancelled, error for failed),
3465
+ msg and
3466
+ ts, startedAt epoch milliseconds when the run finished and started
3467
+ id the run
3468
+ project, workspace, branch
3469
+ project root, workspace id and the branch checked out
3470
+ platform ios, android or macos
3471
+ scheme, variant, configuration
3472
+ the iOS scheme when one was selected, the Android variant,
3473
+ the build configuration
3474
+ placement { where: local | offloaded, machine?, fellBack?, reason? };
3475
+ machine is the build machine that compiled the app, and
3476
+ fellBack with reason marks a build that considered
3477
+ offloading and compiled here
3478
+ cache { result: hit | miss | bypassed, source?: local | remote,
3479
+ key, fingerprint, missReason? }
3480
+ durationMs, phases
3481
+ wall time and milliseconds in each local phase (prepare,
3482
+ cache-lookup, wait, prebuild, pods, compile, device,
3483
+ install, launch)
3484
+ offloadPhases milliseconds on the build machine: sync, deps, prebuild,
3485
+ pods, build, fetch
3486
+ outcome succeeded, failed or cancelled
3487
+ failure failed or cancelled runs: { code, message, remedy, phase }
3488
+ device { name, runtime? } of the simulator, emulator or device;
3489
+ never its serial or UDID
3490
+ stimVersion the stim that ran the build
3491
+ A run that stopped before it had an app to build (a refused flag, a missing
3492
+ project) writes no record. Writing the record never changes the run's result.
3493
+
3347
3494
  REMOTE REQUEST RECORDS
3348
3495
  A request to another machine that fails is a warn record with src: build in the
3349
3496
  run's build log: remote_connect_failed { host, capability, ms, timeoutMs, msg,
@@ -3373,16 +3520,45 @@ Events: run_start, run_end { exit, ms }, exec { program, ms, ok, exit? }, and
3373
3520
  remote_connect and remote_request { ms, ok, code? } for requests to another
3374
3521
  machine. They carry no arguments, tokens or tickets.
3375
3522
 
3523
+ TIMING TRACES
3524
+ STIM_TRACE=<path> makes a stim command write a timeline of where it spent its
3525
+ time, as Chrome trace events. Open the file in Perfetto (ui.perfetto.dev) or
3526
+ chrome://tracing. Unset, nothing is written and no file is opened. There is no
3527
+ setting for it, and the trace goes only to that file, never to stdout or stderr.
3528
+ Use a new path for each command: the command you start truncates the file, and
3529
+ the processes it starts (a Metro supervisor, collectors, a nested stim) append
3530
+ to it.
3531
+ STIM_TRACE=/tmp/ios.json stim ios
3532
+ The file is a JSON array with one event per line and no closing bracket, so
3533
+ read it with JSON.parse(text.trimEnd().replace(/,$/, '') + ']'). Every event
3534
+ has pid; spans have ts and dur in microseconds since the epoch. Events:
3535
+ command one span named "stim <command>" with the exit code in args.exit
3536
+ exec one span per child process. args.argv (or args.command for a shell
3537
+ command) has the values of secret-looking flags, credentials in
3538
+ URLs and token shapes replaced by [redacted], and the credentials
3539
+ of curl -u and security import -P; other short flags such as -p
3540
+ and -u are kept. args also has cwd, ok, exit and signal
3541
+ phase the build phases of an ios, android or macos run on a
3542
+ "phases: <platform> build" track, named as in stats.json and the
3543
+ build ledger (prepare, cache-lookup, wait, prebuild, pods, compile,
3544
+ device, install, launch), and the steps of worktree warm
3545
+ span fingerprint, wait for metro and carry ignored entries
3546
+ mark instant events such as a fingerprint memo hit
3547
+ Children running at the same time are drawn on separate tracks. A span still
3548
+ open when its process exits is written with unfinished: true; a process ended
3549
+ by a signal writes none. A trace holds
3550
+ full paths and arguments, so treat it like a log.
3551
+
3376
3552
  Placement records use src: placement and sit in the run's build log
3377
3553
  (build-*.ndjson), so a new run replaces the previous run's. A run writes one
3378
3554
  record per decision it makes (a build that compiles and has a remote machine to
3379
- consider, or ios/android --remote auto), with event build_placement,
3555
+ consider, or ios/android --remote auto|remote|remote-only), with event build_placement,
3380
3556
  device_placement or placement_fallback. A cache hit, a project with no paired
3381
3557
  remote machine and a named --remote target write none:
3382
3558
  stim logs --source placement
3383
3559
  stim logs --source placement --json
3384
3560
  Fields: kind (build or device), platform, settings [{ key, value, from }] with
3385
- from flag, env, setting or default (remote.build and remote.buildMode for a
3561
+ from flag, env, setting or default (remote.build for a
3386
3562
  build, ios.remote or android.remote, plus remote.easFallback when it is on, for
3387
3563
  a device), candidates [{ machine, code,
3388
3564
  msg, detail? }], choice { machine, code, msg } with machine local when the run
@@ -3403,6 +3579,10 @@ they stay in the local logs. These records carry no tokens.
3403
3579
 
3404
3580
  WHAT WRITES WHAT
3405
3581
  maintenance.ndjson workspace maintenance actions, failures and explaining skips
3582
+ $STIM_HOME/gc/gc.ndjson machine-wide, not in a workspace: what gc, maintenance
3583
+ and the budget gate cleared (stim logs --source gc)
3584
+ $STIM_HOME/builds/builds.ndjson machine-wide: the last 500 native builds
3585
+ (stim logs --source builds)
3406
3586
  metro.ndjson the bundler, in every supervisor mode. Stim's
3407
3587
  middleware adds one record per bundle request
3408
3588
  (bundle_response_started, _progress, _finished,
@@ -3425,7 +3605,8 @@ WHAT WRITES WHAT
3425
3605
  -- and, on iOS, where every Apple framework running in
3426
3606
  the app's process also logs. The proven noise sources
3427
3607
  are recorded at info rather than error (Synchronous
3428
- URL loading diagnostics at warn); the rest is why
3608
+ URL loading diagnostics and SwiftUI Invalid Configuration
3609
+ faults at warn); the rest is why
3429
3610
  --errors leaves this source out unless asked. A VERIFIED
3430
3611
  LAUNCH counts these records and prints one line:
3431
3612
 
@@ -3436,7 +3617,7 @@ WHAT WRITES WHAT
3436
3617
  The count does not attribute unknown OS errors to the app.
3437
3618
  Known JavaScript errors also print individually: Android
3438
3619
  ReactNativeJS records, and iOS com.facebook.react.log /
3439
- javascript records. These can interrupt an optional
3620
+ javascript records. These can interrupt a recommended
3440
3621
  readiness wait even without a client or Metro copy.
3441
3622
  Client and Metro errors still print individually.
3442
3623
  A native app that loaded its bundle but reported app errors
@@ -3618,7 +3799,12 @@ Branch on the code, never on the message.`,
3618
3799
  hosted session. Auto skips declined offers before building, then uses the
3619
3800
  local queue if none admits. A reserve refusal after an accepted offer fails
3620
3801
  with this code and asks to retry; it does not re-place the built architecture.
3621
- Check stim-server and Tailscale there,
3802
+ remote-only never runs here and never joins the local queue: it refuses
3803
+ with this code when no host could ever take the run, and waits for a host that
3804
+ is only full or loaded (see STIM_AT_CAPACITY).
3805
+ An unreachable host's message and remedy are the diagnosis stim doctor gives
3806
+ (Tailscale off, Mac offline with last seen, serve route, stim-server), without
3807
+ tailscale ping. Check stim-server and Tailscale there,
3622
3808
  then run stim doctor. Correct --device-type / --runtime for iOS or
3623
3809
  --system-image / --device-profile for Android when the offer names
3624
3810
  an unavailable choice. A session that exists stays recorded even if delivery
@@ -3740,9 +3926,14 @@ Branch on the code, never on the message.`,
3740
3926
  note naming which of the two set it; Gemfile.lock is never edited either way.
3741
3927
  \`worktree warm --refresh\` reports the same code for the install it runs in
3742
3928
  the SOURCE CHECKOUT -- the lockfile's own command (\`pnpm install\`,
3743
- \`yarn install\`, \`bun install\`, \`npm ci\`) or that same pod ladder. The
3929
+ \`yarn install\`, \`bun install\`, \`npm ci\`), the \`npm run postinstall\`
3930
+ it runs when npm's config there sets ignore-scripts, or that same pod ladder. The
3744
3931
  message names the command, quotes its last lines, and nothing is copied: fix
3745
- the source checkout, then warm again.`
3932
+ the source checkout, then warm again.
3933
+ start, ios and android also refuse with this code when \`gc --delete\`
3934
+ lightened the worktree and the \`stim worktree warm\` they ran to restore
3935
+ its node_modules and ios/Pods failed. The warm's own lines say why; fix
3936
+ that, then run \`stim worktree warm\` or retry the command.`
3746
3937
  },
3747
3938
  STIM_BUILD_FAILED: {
3748
3939
  summary: "xcodebuild or gradle failed; the Android missing-SDK and APK refusals",
@@ -3980,6 +4171,11 @@ and no enabled remote can finish this build, or when the build requires a local
3980
4171
  compiler. Enable local or choose an explicit --remote-build placement. Existing
3981
4172
  builds are not interrupted and a cache hit needs no compiler.
3982
4173
 
4174
+ remote-only never builds here: it refuses when no remote machine in remote.machines
4175
+ can take the build, naming each machine and why it was skipped, and for a build that
4176
+ cannot leave this machine. Rerun with --remote-build remote to allow a local build
4177
+ when no remote machine can take it.
4178
+
3983
4179
  A named --remote-build selection is strict. The message names the machine
3984
4180
  and why it cannot take or finish the build: not configured or paired, approval
3985
4181
  pending or denied, unreachable, changed pinned server key, a stim-server
@@ -3987,7 +4183,12 @@ too old to speak protocol 2, incompatible
3987
4183
  toolchain/runtime/CPU, low disk, busy, sync/build failure, artifact failure,
3988
4184
  or a checkout that changed while it built. Unsupported build kinds also refuse.
3989
4185
  No local xcodebuild, Gradle or SwiftPM compile or alternate machine follows a
3990
- refusal. Prebuild and pod install still run on this machine before a named offload.
4186
+ refusal. For stim ios, a toolchain difference on the named machine, such as Xcode,
4187
+ the simulator runtime or the CPU, refuses before this machine runs prebuild or pod
4188
+ install, even when prebuild would have produced an app the local cache already holds, or the
4189
+ machine already holds the exact artifact. A
4190
+ missing or different global CocoaPods never refuses a named machine; a pod install
4191
+ that fails on it reports "pod install failed" with the command to run there.
3991
4192
  Unlisted or unpaired names refuse at setup before consulting the cache, without
3992
4193
  a build record or failed-run stats. A listed paired name with a cache hit needs
3993
4194
  no build and does not contact the selected machine.
@@ -4003,7 +4204,7 @@ Run stim doctor --fix to ask for build access if not paired. Check
4003
4204
  stim settings get remote.machines. A person on the worker approves this machine
4004
4205
  by running the stim-server setup command doctor --fix printed, with its ticket
4005
4206
  and expiry (guide settings). Agents never run setup or approve requests.
4006
- Rerun with --remote-build auto for normal placement and local fallback, or
4207
+ Rerun with --remote-build auto or remote for placement with local fallback, or
4007
4208
  --remote-build local to keep the build here.
4008
4209
  `
4009
4210
  },
@@ -4098,6 +4299,13 @@ Rerun with --remote-build auto for normal placement and local fallback, or
4098
4299
  Trust, then run the command again. It is a per-developer-certificate tap, not
4099
4300
  a per-build one -- but an uninstall clears it, including the one Stim's own
4100
4301
  signer-conflict retry performs.
4302
+ On a local iOS simulator, a cache-hit app that dyld kills at launch because
4303
+ one part of the app cannot bind a symbol from another part of the same app
4304
+ (for example React.framework against hermesvm.framework) is a damaged build,
4305
+ not a launch problem. Stim removes that cache entry, and the remedy names
4306
+ it. Run the command again to build the app fresh. A cache provider's copy is
4307
+ not removed; add \`--no-build-cache\` to skip it. If the fresh app crashes
4308
+ the same way, the project's own native code is the cause.
4101
4309
  With \`--remote eas\` the remedy names the EAS Simulator session that is
4102
4310
  still running, as for STIM_INSTALL_FAILED.`
4103
4311
  },
@@ -4379,6 +4587,16 @@ so a Debug run on one is wired to a LAN origin instead of localhost.`,
4379
4587
  installation. If it fails, the refusal includes observed host memory pressure
4380
4588
  when available. Free memory before retrying under pressure; see
4381
4589
  \`stim guide lifecycle simslim\`. Booted alone does not prove readiness.
4590
+ The same probe lists the simulator's launchd jobs and requires a running
4591
+ SpringBoard. A simulator that reports Booted while SpringBoard has died
4592
+ (\`simctl launch\` then says "The system shell probably crashed") is shut
4593
+ down through teardown and booted once more; if SpringBoard is still not
4594
+ running, the refusal names the udid and the remedy: \`stim stop\`, then
4595
+ \`stim ios\` again. Repeated SpringBoard crashes leave reports in
4596
+ ~/Library/Logs/DiagnosticReports, and macOS deletes them after about two
4597
+ days; before the restart Stim copies the simulator's recent ones into the
4598
+ workspace logs directory and records the boot steps by udid
4599
+ (\`stim logs --source device\`, \`stim guide logs\`, IOS BOOT RECORDS).
4382
4600
  On Android the emulator's own stdio is captured to
4383
4601
  the global workspace logs/emulator.log (truncated per boot), and when it printed a
4384
4602
  \`FATAL |\` / \`ERROR |\` / \`PANIC:\` line THAT is the message and the remedy
@@ -4481,6 +4699,11 @@ so a Debug run on one is wired to a LAN origin instead of localhost.`,
4481
4699
  locks, builds, viewers and recent activity. Reclaim excludes this workspace,
4482
4700
  physical, hosted, remote, parked and other homes' devices and never deletes.
4483
4701
  A failed reclaim is logged and skipped; the run keeps waiting.
4702
+ --remote remote-only (and macos.remote remote-only) has no local queue: when every
4703
+ approved host is full, loaded or short of memory it polls the hosts every 5 seconds
4704
+ for the same wait (--wait, default 600 seconds) and then refuses with this code;
4705
+ --no-wait and --wait 0 refuse at once. Builds under remote-only refuse with
4706
+ STIM_OFFLOAD_REFUSED instead.
4484
4707
  Stop an environment (\`stim stop\`), pass a longer \`--wait <seconds>\`,
4485
4708
  or raise concurrency.maxDevices. With --remote auto, remote.machines and
4486
4709
  the opt-in remote.easFallback (billed EAS Simulator) are tried before this
@@ -4920,8 +5143,8 @@ Inspect what remains with gc() before retrying a mutation.
4920
5143
  `
4921
5144
  },
4922
5145
  STIM_CANCELLED: {
4923
- summary: "an ios or android run was interrupted by `stim stop` or Ctrl-C before it finished",
4924
- body: () => `STIM_CANCELLED (ios, android; exit 130)
5146
+ summary: "an ios, android or macos run was interrupted by `stim stop` or Ctrl-C before it finished",
5147
+ body: () => `STIM_CANCELLED (ios, android, macos; exit 130)
4925
5148
  "The ios run was cancelled by \`stim stop\` (pid <n>) before it finished."
4926
5149
  The run was interrupted: by \`stim stop\`, which cancels a build it leaves
4927
5150
  with nothing to deploy to, or by Ctrl-C. It forwarded the interrupt
@@ -4930,8 +5153,11 @@ Inspect what remains with gc() before retrying a mutation.
4930
5153
  stopped at. An interrupted build stores no artifact, so the next run looks
4931
5154
  up the cache as usual. Stopped between build tools, a run that \`stop\`
4932
5155
  cancelled starts no further build tool and stops before it installs. A
4933
- second Ctrl-C exits at once, and so does a Ctrl-C with no build tool or
4934
- SimSlim to stop; those exits print no payload. Run the same command again when you want the
5156
+ Ctrl-C while the run delivers the app to a hosting Mac, or waits for the app
5157
+ to load its bundle or report readiness, ends that work and reports
5158
+ STIM_CANCELLED. A second Ctrl-C exits at once, and so does a Ctrl-C with no
5159
+ build tool, SimSlim, hosted app delivery or launch readiness wait to stop;
5160
+ those exits print no payload. Run the same command again when you want the
4935
5161
  app on a device.`
4936
5162
  },
4937
5163
  STIM_STOP_BLOCKED: {
@@ -5144,8 +5370,9 @@ not on any remote" (worktree remove)
5144
5370
  lockfile says nothing about the one on disk now, whose dependencies may well
5145
5371
  have been installed since; and a repository with no record at all -- every
5146
5372
  repository before its first \`--refresh\` -- copies as it always has.
5147
- In a monorepo the record is keyed on the directory that owns the lockfile,
5148
- which is usually the repository root, so every app of it reads the same one.`
5373
+ Each directory that owns a lockfile the refresh installs has its own record:
5374
+ the repository root and, when the app has a lockfile of its own, the app. A
5375
+ plain warm reads both.`
5149
5376
  },
5150
5377
  warm: {
5151
5378
  summary: "worktree warm claim storage refusals and recovery",
@@ -5442,10 +5669,11 @@ simulators to volume zero through Xcode's devicectl, and Android 15 and later
5442
5669
  emulators to media volume zero. The iOS mute runs after SimSlim has applied its
5443
5670
  profile and once SpringBoard has finished starting, because both a SimSlim
5444
5671
  reboot and SpringBoard's startup reset the volume. A device that is already
5445
- running keeps its volume, except that SimSlim reboots a running simulator whose
5446
- profile changed. Turn sound on for one device with Sound (iOS) or
5447
- Media sound (Android) in its Stim Desktop options; the choice lasts until that
5448
- device next boots. Android 14 and older images have no shell command that can
5672
+ running keeps its volume, except that a running simulator SimSlim reboots (a
5673
+ first slim, a profile change, or restoring stock services) is muted again once
5674
+ SpringBoard is back; an already slim simulator is not touched. Turn sound on
5675
+ for one device with Sound (iOS) or Media sound (Android) in its Stim Desktop
5676
+ options; the choice lasts until that device next boots. Android 14 and older images have no shell command that can
5449
5677
  set media volume, so startup warns, as it does whenever a mute is not
5450
5678
  confirmed. Host volume, Android alarm and ringtone volumes, hosted devices and
5451
5679
  physical devices are unchanged.
@@ -5840,9 +6068,9 @@ If Stim is not installed globally, replace stim with npx stim.
5840
6068
 
5841
6069
  A person on the host approves device-host access, separately from build, read
5842
6070
  and control. The setup in hosted-ios also serves Android. android.remote sets
5843
- a workspace default. eas and proxy remain backends. --remote auto or
5844
- android.remote = auto places automatically; see hosted-ios for the shared
5845
- policy. No flag or setting runs on this machine.
6071
+ a workspace default. eas and proxy remain backends. --remote auto, remote or
6072
+ remote-only (or the same android.remote value) places automatically; see
6073
+ hosted-ios for the shared policy. No flag or setting runs on this machine.
5846
6074
 
5847
6075
  A named Mac is strict: refusal, unreachable, declined or failed preparation
5848
6076
  returns STIM_HOSTING_REFUSED without fallback. --device with --remote
@@ -5969,8 +6197,8 @@ Logcat retains a finite buffer; records already evicted or persisted beyond the
5969
6197
  overlap may be unavailable. An oversized query retries the recent tail and writes
5970
6198
  a device warning naming any dropped interval. Followers share a child-aware log
5971
6199
  claim; install and stop cancel and settle the worker before native operations.
5972
- Stop copies logs before and after the host's final collection and teardown, with
5973
- each client drain bounded to 30 seconds and progress on stderr. Collected logs
6200
+ Stop copies logs once, after the host's final collection and teardown, bounded to
6201
+ 30 seconds with progress on stderr; Ctrl-C cancels the copy and keeps the placement. Collected logs
5974
6202
  remain readable after stop. The host advertises hosted-android-data for Android
5975
6203
  handoff and native logs. Older hosts get a newer stim-server note, use upload
5976
6204
  and show records already copied here.`
@@ -5990,28 +6218,44 @@ If Stim is not installed globally, replace stim with npx stim.
5990
6218
  stim reload ios
5991
6219
  stim stop
5992
6220
 
5993
- The same setup serves macos --remote. Approval is separate from build, read
6221
+ The same setup serves macos --remote and macos.remote. Approval is separate from build, read
5994
6222
  and phone control. Set ios.remote to the machine name for a default (machine scope for every app on this machine);
5995
6223
  no flag or setting runs here. eas and proxy keep their remote backend meanings.
5996
6224
  --remote auto (or ios.remote = auto / android.remote = auto) stays here when
5997
6225
  there is a free concurrency.maxDevices slot (0 means unlimited), no device
5998
6226
  waiter ahead, normal host memory pressure, no budget shortfall,
5999
6227
  and 5-minute load per core below server.maxLoadPerCore. Without remote.machines,
6000
- auto is local. An unknown local device count also stays local; boot admission
6228
+ auto and remote are local and remote-only refuses with STIM_HOSTING_REFUSED. An unknown local device count also stays local; boot admission
6001
6229
  still decides. The default without a flag or setting stays local. remote.devicePoolDisabled excludes
6002
6230
  members from new automatic placement only; an excluded local member is never a
6003
6231
  fallback. Existing sessions keep their owner and named placement bypasses the pool.
6004
6232
  See stim guide settings for separate build/device membership controls.
6005
6233
 
6234
+ --remote remote (or ios.remote = remote) skips that local check and asks the hosts
6235
+ first, in remote.machines order; the simulator runs here only when none admits
6236
+ it, with the same wait and EAS fallback as auto. --remote remote-only never runs
6237
+ here and never joins the local queue or uses EAS. When every host that answered is
6238
+ only full, loaded or short of memory, it waits for one to have room, polling every
6239
+ 5 seconds for --wait seconds (default 600) and printing one placement line; --no-wait,
6240
+ --wait 0 or the end of the wait refuse with STIM_AT_CAPACITY. When no host can ever take
6241
+ the run (none configured, unreachable, declined for another reason such as paused),
6242
+ it refuses at once with STIM_HOSTING_REFUSED, naming each skipped host and its reason.
6243
+ --plan never waits. It prints the same STIM_HOSTING_REFUSED, or a STIM_BAD_ARG naming
6244
+ an invalid remote.build or STIM_REMOTE_BUILD.
6245
+ Excluding local in remote.devicePoolDisabled has the same effect on auto and remote.
6246
+
6006
6247
  Otherwise Stim asks every approved remote.machines host in parallel, skipping
6007
6248
  one that tailscale status reports as running an OS other than macOS, with a
6008
6249
  3-second probe timeout and this run's model/runtime or image/profile selectors.
6009
- A compatible choice, no declined reason, normal memory pressure and available
6010
- capacity are required. With a free local slot, a host must report a lower load;
6011
- with a full cap or a queue ahead, an admitted host with unknown load is allowed.
6012
- Hosts rank by the build preference (flag > STIM_REMOTE_BUILD > remote.build)
6013
- when it names a machine, then lowest load, most free memory and remote.machines order. Automatic build
6014
- offload resolves later, after the device architecture is known.
6250
+ A compatible choice, no declined reason, normal memory pressure, available
6251
+ capacity and a load per core under the host's own server.maxLoadPerCore are
6252
+ required (a host that reports no limit uses this machine's). With a free local
6253
+ slot, a host with unknown load is skipped under auto (remote admits it); with a full cap or a queue ahead, an
6254
+ admitted host with unknown load is allowed. Admitted hosts are tried in order:
6255
+ the host named by the build preference (flag > STIM_REMOTE_BUILD > remote.build)
6256
+ when it names a machine, then remote.machines order, so the first listed host with
6257
+ room fills before the next gets a run. Load and free memory do not rank hosts.
6258
+ Automatic build offload resolves later, after the device architecture is known.
6015
6259
 
6016
6260
  When no host admits and this machine is at its concurrency.maxDevices cap (or runs
6017
6261
  are queued ahead), remote.easFallback (default false; EAS Simulator is billed)
@@ -6028,7 +6272,7 @@ metro.tunnel off; on an Expo tunnel, run stim start --remote first). A recorded
6028
6272
  EAS session is not sticky: once this machine has room, auto runs here and the
6029
6273
  session bills until stim stop or remote.easMaxDurationMinutes. If any check fails, or the
6030
6274
  setting is off, auto runs here and may wait in the existing FIFO device
6031
- slot queue; --no-wait and --wait 0 refuse with STIM_AT_CAPACITY. The placement
6275
+ slot queue; --no-wait and --wait 0 refuse with STIM_AT_CAPACITY (remote-only has no local queue and waits for a remote host instead, as described above). The placement
6032
6276
  line explains the decision, the skipped hosts and why EAS was not used
6033
6277
  (machine "eas"). JSON progress goes to stderr.
6034
6278
  The same decision, with a reason code per host, is a src: placement record in
@@ -6040,8 +6284,8 @@ A reserve refusal after a successful offer can race with another run. It fails
6040
6284
  with STIM_HOSTING_REFUSED naming the host and asking to retry. Stim does not
6041
6285
  try the next host or local placement after the build has targeted that host's
6042
6286
  architecture, and never falls back once a hosted session exists. A named machine remains strict. --device
6043
- cannot combine with --remote auto, and a --device run ignores ios.remote and
6044
- android.remote. eas/proxy and macOS placement are unchanged.
6287
+ cannot combine with --remote auto, remote or remote-only, and a --device run ignores ios.remote and
6288
+ android.remote. eas/proxy placement is unchanged; macOS placement follows macos.remote (stim guide macos).
6045
6289
  --simulator-app is honoured locally with auto and ignored with a note on a host.
6046
6290
  Placement measures budgets without reclaiming; only a new local run reclaims.
6047
6291
  Android --remote <machine> uses an owned emulator on that Mac; see hosted-android.
@@ -6320,8 +6564,8 @@ command before any EAS lookup. stim doctor reports it in a project with eas.json
6320
6564
  If another run holds the artifact claim, wait for it to finish and retry.`
6321
6565
  },
6322
6566
  readiness: {
6323
- summary: "implement optional pending/ready app logs, deadlines, errors, and platform isolation",
6324
- body: () => `OPTIONAL APP READINESS
6567
+ summary: "implement recommended pending/ready app logs, deadlines, errors, and platform isolation",
6568
+ body: () => `RECOMMENDED APP READINESS
6325
6569
 
6326
6570
  No package or SDK is needed. This applies to debug ios/android launches with
6327
6571
  Metro verification enabled, not release builds or --no-metro-check.
@@ -6382,7 +6626,10 @@ Only exact standalone info/debug messages captured from this app's device log
6382
6626
  for this platform and current launch are accepted. Stale, other-platform,
6383
6627
  error-level, and embedded example messages are ignored. Unlabelled shared
6384
6628
  Metro output cannot identify a platform. If pending is not captured in time,
6385
- including when device logs are unavailable, the default check applies.
6629
+ including when device logs are unavailable, the default check applies. On a
6630
+ device hosted on another Mac (--remote <machine>), the device log is the copy
6631
+ pulled from that Mac during the launch check, and each window waits up to 10
6632
+ seconds for the copy to cover it.
6386
6633
 
6387
6634
  The app declares readiness; Stim does not inspect a rendered frame. This does
6388
6635
  not change launched JSON semantics or the need to inspect the expected UI and
@@ -6674,7 +6921,8 @@ result as proof instead of requiring an unrelated screenshot.`
6674
6921
 
6675
6922
  \`gc --older-than <days>\` reports and deletes, or with \`--cache parked\`
6676
6923
  erases, only the devices parked at least that long; one parked at an unknown
6677
- time stays.
6924
+ time stays. Automatic maintenance's daily age sweep deletes the devices
6925
+ parked for maintenance.olderThanDays (7; 0 never) the same way.
6678
6926
 
6679
6927
  If simulator listing or deletion fails, \`gc --delete\` reports the failure
6680
6928
  and keeps that entry. It never turns an unverified absence into a dropped
@@ -6846,11 +7094,27 @@ IOS SCHEME SELECTION
6846
7094
 
6847
7095
  AN ARTIFACT THE DEVICE ALREADY HOLDS IS NOT INSTALLED AGAIN
6848
7096
  Both platforms store the artifact verbatim, so its hash is its identity.
6849
- Before installing, Stim hashes the artifact it is about to install and the
6850
- one the device already has -- \`pm path\` then \`sha256sum\` on Android, the
6851
- \`simctl get_app_container\` bundle on iOS. Byte-identical means the install
6852
- is skipped. The phase still reports the cost of proving that identity, but
6853
- avoids the ~43s a 400MB APK can cost to copy and install over USB.
7097
+ Before installing, Stim compares the artifact it is about to install with
7098
+ the one the device already has. On iOS it hashes both bundles: the
7099
+ \`simctl get_app_container\` bundle and the one about to be installed.
7100
+ Byte-identical means the install is skipped. The phase still reports the
7101
+ cost of proving that identity, but avoids the ~43s a 400MB APK can cost to
7102
+ copy and install over USB.
7103
+
7104
+ On Android the proof is one \`adb shell\` call that returns the installed
7105
+ APK's path, size, and the SHA-256 of its tail: the v2 or v3 signing block,
7106
+ the central directory, and the end record. That tail carries the signed
7107
+ digests of every other byte, and Android verified them when it installed
7108
+ the APK. Stim verifies the local APK against the same digests once, with
7109
+ \`apksigner verify\`, and keeps the size and tail digest in
7110
+ \`apk-signed-tail.json\` beside the APK in its build cache entry (stored with
7111
+ the build; an older entry gets it on first use). The same size and tail
7112
+ digest mean the same file, on an owned emulator, an adopted one, a
7113
+ \`--device\` phone and a plain rerun. A different size or tail digest
7114
+ installs without hashing the whole APK. An APK outside a build cache entry,
7115
+ one without a v2 or v3 signing block, an apksigner that is missing or fails,
7116
+ and a device whose \`tail\` or \`stat\` does not answer as expected fall back
7117
+ to \`pm path\` then \`sha256sum\` of the whole installed APK.
6854
7118
 
6855
7119
  install unchanged (emulator-5584 already has this build) (0.4s)
6856
7120
 
@@ -6863,8 +7127,9 @@ AN ARTIFACT THE DEVICE ALREADY HOLDS IS NOT INSTALLED AGAIN
6863
7127
  install unchanged (stim-app already has this build) (0.4s)
6864
7128
  install dev client prepared (0.9s)
6865
7129
 
6866
- The skip needs PROOF. A package that is not installed, a split install, an
6867
- image without \`sha256sum\`, and any adb or simctl failure all read as
7130
+ The skip needs PROOF. A package that is not installed, a split install, a
7131
+ device that answers neither probe (no \`tail\` and no \`sha256sum\`), and any
7132
+ adb or simctl failure all read as
6868
7133
  "cannot determine", and the run installs exactly as it always did. A release
6869
7134
  run swaps this workspace's JS into a COPY of the artifact, which is a
6870
7135
  different artifact and is therefore always installed.
@@ -7065,6 +7330,67 @@ THE BUILD CACHE HAS THREE LEVELS
7065
7330
  trims, and clears local caches only: the provider contract has no delete
7066
7331
  operation, so no local command can remove data a team or CI system shares.
7067
7332
 
7333
+ WHAT THE KEY COUNTS. Stim runs @expo/fingerprint with its default
7334
+ \`balanced\` preset, the one the project's own Expo uses. An installed
7335
+ dependency, and a config plugin loaded from node_modules, counts by its name
7336
+ and version, not by its files. A package whose real path is outside
7337
+ node_modules -- a workspace package, a link: dependency, a local Expo module
7338
+ -- counts by its files, and so do patches/, the app's committed ios/ or
7339
+ android/, the evaluated app config and its local plugins. patches/ and
7340
+ .yarn/patches/ next to a monorepo's lockfile count too, and so do the patch
7341
+ hashes pnpm-lock.yaml records, wherever the patch files live. The app's
7342
+ version, ios.buildNumber and android.versionCode, eas.json, .easignore and
7343
+ the path overrides of react-native.config.js are left out, so a Release
7344
+ cache hit keeps the version it was built with. A project that wants native
7345
+ package files hashed sets \`preset: 'strict'\` in fingerprint.config.js, at
7346
+ about twice the fingerprint's CPU.
7347
+
7348
+ AN EDIT INSIDE node_modules/ IS NOT SEEN. Editing a dependency's native file
7349
+ in place (node_modules/<pkg>/ios, android, cpp, a podspec or build.gradle)
7350
+ leaves the key unchanged, so the next run installs the cached app without
7351
+ the edit. Make the change a patch (patch-package's patches/, or pnpm patch,
7352
+ whose patches the fingerprint hashes), or build it with
7353
+ \`--no-build-cache --remote-build local\`. --no-build-cache alone can still
7354
+ place the build on another machine, which installs dependencies from the
7355
+ lockfile and so builds without the edit. The fresh build is stored under the
7356
+ unchanged key in the machine-wide cache and handed to any cache provider, so
7357
+ every worktree reuses it, edit included, until the dependency's version or
7358
+ patches change; undo the edit with another --no-build-cache build.
7359
+
7360
+ A KNOWN TREE SKIPS THE FULL FINGERPRINT. After a full fingerprint at the
7361
+ first lookup of a React Native or Expo run, Stim records it in
7362
+ $STIM_HOME/fingerprint-memo (~/.stim by default) under a static digest of
7363
+ the tree: git blob ids of the native inputs git tracks, the contents of the
7364
+ untracked, modified and ignored ones, the app config's sources and the files
7365
+ and directories it names, each installed package's path, version and native
7366
+ markers, the lockfile, and the environment variables the config and its
7367
+ .env files read. Any worktree whose tree has the same digest, a fresh one
7368
+ included, reuses the record instead of evaluating the config and running
7369
+ autolinking:
7370
+
7371
+ fingerprint matches a recorded fingerprint of this tree; confirming it in the background
7372
+
7373
+ A full fingerprint then runs at low priority while the app installs and
7374
+ launches. The record is put back only when it agrees. If the run reaches its
7375
+ end first, it waits up to 3 seconds more, then gives the check up and leaves
7376
+ no record, so the next run fingerprints in full. A disagreement prints a
7377
+ warning naming both hashes at the end of the run and writes a
7378
+ fingerprint_memo_stale record to the build log; run the command again to use
7379
+ the current tree.
7380
+
7381
+ A config the digest cannot follow always fingerprints in full: a dynamic
7382
+ require or import, process.env used other than by literal name, file,
7383
+ process or network access while it is evaluated, a dotenv import, a
7384
+ fileHookTransform, or a preset that hashes package files. A local config
7385
+ plugin counts as evaluated too, with one exception: the digest does not
7386
+ track the files a plugin that applies mods reads, assuming it reads them in
7387
+ its mods at prebuild. A plugin that also puts what it reads into the config
7388
+ is a gap only the background check catches. So does a project outside git or on Yarn Plug'n'Play. The record is
7389
+ never consulted after prebuild or pod install, by a build worker, by doctor,
7390
+ or with --no-build-cache; ios --plan and android --plan read one without
7391
+ writing. Records are 0600, at most 256 per platform, and gc prunes them by
7392
+ age with the other caches.
7393
+
7068
7394
  A MISS EXPLAINS ITSELF. Each local cache entry keeps the per-source hashes
7069
7395
  of its fingerprint (hashes only, pruned with the entry). Before compiling,
7070
7396
  Stim compares the sources it is about to store with a baseline: the entry
@@ -7301,8 +7627,10 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
7301
7627
  Waiting prints the count, holder workspace names
7302
7628
  and elapsed time at once and about every 10s, then
7303
7629
  the queue position, head workspace name and
7304
- $STIM_HOME/device-waits path. Timeout names the
7305
- same head and path. Stop cancels a queued run and
7630
+ $STIM_HOME/device-waits path. Workspaces that
7631
+ share a project name show the directory that tells
7632
+ them apart, as in mobile (qa-b-p2). Timeout names
7633
+ the same head and path. Stop cancels a queued run and
7306
7634
  releases its ticket without booting its device.
7307
7635
  Status JSON adds build.waitingFor independently
7308
7636
  of phase: { kind: "device-slot" | "build-slot",
@@ -7380,7 +7708,14 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
7380
7708
  (\`gc --delete\` semantics; the next build installs from the shared
7381
7709
  cache). A workspace whose last use is unknown is kept
7382
7710
  4. trim shared cache entries nothing has used for 14 days
7383
- (\`gc --delete --older-than 14\` for the caches)
7711
+ (\`gc --delete --older-than 14\` for the caches). Caches with nothing
7712
+ to trim print one line, not one each. A trim that removed nothing is
7713
+ recorded in $STIM_HOME/budget-sweep.json and not repeated for 5
7714
+ minutes while the set of caches is the same and free disk has not
7715
+ fallen by more than 1 GB since; the run prints one budget line saying
7716
+ it skipped. \`gc\` and automatic maintenance never skip, and a dry
7717
+ run (\`doctor\`) still plans the step. The STIM_LOW_DISK message
7718
+ measures the largest uses of disk at the same time.
7384
7719
 
7385
7720
  IDLE SHUTDOWN (30 MINUTES BY DEFAULT): with devices.idleShutdownMinutes above
7386
7721
  0, a workspace's supervisor shuts down that workspace's owned simulators and
@@ -7397,9 +7732,13 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
7397
7732
  status\` shows "shut down after 30m idle" on the device until the next
7398
7733
  \`ios\` or \`android\` run boots it again. When metro.idleStopMinutes is
7399
7734
  shorter, the dev server's idle stop first shuts down the devices idle that
7400
- long, because no supervisor is left to check afterwards. The supervisor
7401
- path does not check without a supervisor: release runs, and after
7402
- \`stim stop\`. The setting is read when the supervisor starts. Stim Desktop's simulator view is not a
7735
+ long, because no supervisor is left to check afterwards. Devices with no
7736
+ supervisor (release runs, a dev server that exited) are shut down by
7737
+ automatic maintenance while maintenance.mode is on (guide cleanup): every maintenance.idleCheckMinutes
7738
+ (5) a command starts a background check that applies each workspace's
7739
+ current setting with the same idle rules, native-run lock recheck, status
7740
+ and log record, and skips the workspace of the command that started it.
7741
+ The supervisor reads the setting when it starts. Stim Desktop's simulator view is not a
7403
7742
  stim-server client and does not count as a viewer. Change it with
7404
7743
  \`stim settings set devices.idleShutdownMinutes <minutes> --scope machine\`;
7405
7744
  0 turns it off, and an explicit 0 stays 0.
@@ -7445,8 +7784,8 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
7445
7784
  summary: "every flag per command, Android variants and flavors, the per-run simulator model, runtime and system image",
7446
7785
  body: () => `THE OPTION SURFACE, IN FULL
7447
7786
  start --json --wait <seconds> --remote --reset-cache
7448
- ios --remote-build <auto|local|name> --slot <name> --json --plan --no-metro-check --no-build-cache --scheme <name> --configuration <name> --device-type <name> --runtime <version> --simulator-app <xcode|siniulator|stim-desktop> --device [udid] --wait <seconds> --no-wait --remote <eas|proxy|auto|machine>
7449
- android --remote-build <auto|local|name> --slot <name> --json --plan --no-metro-check --no-build-cache --variant <name> --system-image <id> --device-profile <id> --device [serial] --wait <seconds> --no-wait --remote <proxy|eas>
7787
+ ios --remote-build <local|auto|remote|remote-only|name> --slot <name> --json --plan --no-metro-check --no-build-cache --scheme <name> --configuration <name> --device-type <name> --runtime <version> --simulator-app <xcode|siniulator|stim-desktop> --device [udid] --wait <seconds> --no-wait --remote <eas|proxy|local|auto|remote|remote-only|machine>
7788
+ android --remote-build <local|auto|remote|remote-only|name> --slot <name> --json --plan --no-metro-check --no-build-cache --variant <name> --system-image <id> --device-profile <id> --device [serial] --wait <seconds> --no-wait --remote <proxy|eas|local|auto|remote|remote-only|machine>
7450
7789
  ios/android --wait bounds owned-device slot waits (default 600s), or physical
7451
7790
  leases with --device (default 60s). Remote targets do not join this local
7452
7791
  slot queue. --no-wait refuses a full/queued owned
@@ -7554,12 +7893,24 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
7554
7893
  .stim.json, else \`git symbolic-ref --short refs/remotes/origin/HEAD\`;
7555
7894
  when neither answers it says so and makes no warning.
7556
7895
 
7557
- Dependencies install where the lockfile is, which in a monorepo is the
7558
- repository root, when the lockfile moved or nothing is installed. Pods run
7559
- for the app the command was invoked from, and only that app, when its
7560
- ios/Podfile.lock moved or ios/Pods/Manifest.lock does not match it. Each
7561
- completed or skipped deps and pods step names its source directory and
7562
- reason. A failed install refuses with STIM_DEPS_FAILED and nothing is copied.
7896
+ The app is the one warm prepares: the app the command was invoked from, or,
7897
+ from a monorepo root, the one app registered in the source checkout or in
7898
+ the worktree. With no such app, a refresh from the root updates only the
7899
+ repository root and says so on stderr.
7900
+ Dependencies install where the repository root's lockfile is and, when the
7901
+ app has its own lockfile (a nested package outside the root workspaces),
7902
+ there too; a root that has neither installed dependencies nor an install
7903
+ record is left alone when the app has its own lockfile. Each installs when
7904
+ its lockfile moved, when its patches/ directory moved (npm projects only),
7905
+ when nothing is installed, or when npm reports a tree that differs from the
7906
+ one it reported after the last completed install. An npm project whose
7907
+ npm config sets ignore-scripts gets no postinstall from \`npm ci\`, so when its
7908
+ package.json defines one the refresh runs \`npm run postinstall\` next
7909
+ (patch-package, usually); dependencies' own install scripts stay blocked.
7910
+ Pods run for that app, and only that app, when its ios/Podfile.lock moved or
7911
+ ios/Pods/Manifest.lock does not match it. Each completed or skipped deps and
7912
+ pods step names its source directory and reason. A failed install or
7913
+ postinstall refuses with STIM_DEPS_FAILED and nothing is copied.
7563
7914
 
7564
7915
  An install that did not finish is remembered, and a PLAIN warm reads that
7565
7916
  before it copies. The refresh records the completed install of the lockfile it
@@ -7624,7 +7975,11 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
7624
7975
  expo-modules-jsi/apple/.DerivedData Xcode build state, directly under
7625
7976
  node_modules or in the pnpm virtual store. An ignored directory that Git
7626
7977
  reports above those names (for example an app directory with no tracked
7627
- files) is copied as is. Warm also skips paths
7978
+ files) is copied as is. Warm renames those deleted paths into
7979
+ $STIM_HOME/trash and a detached, low-priority rm deletes them after warm
7980
+ returns (through the programmatic API, before the call resolves); when the
7981
+ rename is not possible (Windows, $STIM_HOME on another volume), warm deletes
7982
+ them before it returns. Warm also skips paths
7628
7983
  overlapping a nested destination worktree or below a symlink ancestor.
7629
7984
  Tracked .idea settings come from Git and stay untouched by warm.
7630
7985
 
@@ -8160,17 +8515,23 @@ THE POOL: WHICH DEVICE AN ID-LESS \`--device\` PICKS
8160
8515
  \`ios.simslim off\` opts that app out and restores stock services on its
8161
8516
  simulator. The defaults disable push, StoreKit, iCloud, Safari sync, Mail,
8162
8517
  Calendar and Contacts services, among others; move an app that needs them
8163
- to a profile that keeps those categories, or to \`off\`. A running or
8164
- parked simulator is slimmed the next time Stim boots or adopts it, and an
8165
- already slim simulator is left alone, so only the first slim reboots it.
8518
+ to a profile that keeps those categories, or to \`off\`. A running
8519
+ simulator is slimmed on the next \`stim ios\`, without a boot first, and a
8520
+ parked simulator when Stim adopts it. An already slim simulator is left
8521
+ alone, so only the first slim reboots it.
8166
8522
  Without the \`simslim\` command, doctor reports it and \`stim ios\` boots
8167
8523
  the simulator unslimmed with a one-line note. Hosted simulators on a named
8168
8524
  Mac are never slimmed, whatever either Mac's setting says.
8169
8525
 
8170
8526
  SimSlim 0.8 requires iOS 18.5 or newer. On each local \`stim ios\`,
8171
8527
  Stim reconciles that profile on the owned simulator before the app build.
8172
- The first change can update services and reboot the simulator. A matching
8173
- profile is a fast no-op on later launches. The settings persist across normal
8528
+ The first change can update services and reboot the simulator. Stim records
8529
+ the applied profile (the default categories, or a hash of the profile file)
8530
+ and the boot of the simulator it was applied on, so a later launch on the
8531
+ same boot with the same profile skips the \`simslim\` call. A changed
8532
+ profile, a reboot, or a missing record reconciles again. A manual
8533
+ \`simslim off\` on a running simulator is not noticed until the simulator
8534
+ reboots or the profile changes. The settings persist across normal
8174
8535
  shutdowns and reboots. Removing the setting restores stock services when
8175
8536
  Stim applied the profile. Stim never changes an unowned or remote simulator.
8176
8537
  Each SimSlim operation has a 12-minute outer deadline, including discovery.
@@ -8267,13 +8628,14 @@ and logs, and never deletes anything. off disables it. It is off in CI and
8267
8628
  scoped STIM_HOME homes unless STIM_MAINTENANCE is explicit, so a scratch home
8268
8629
  or CI run never deletes on its own.
8269
8630
  Commands trigger a detached pass when a check is due: disk and memory pressure
8270
- (every minute), directory sizes (hourly), finished worktrees (every 15
8271
- minutes) and the age sweep (daily). guide, settings, help and every gc
8631
+ (every minute), idle devices (every 5 minutes, maintenance.idleCheckMinutes),
8632
+ directory sizes (hourly), finished worktrees (every 15 minutes) and the age
8633
+ sweep (daily). guide, settings, help and every gc
8272
8634
  invocation do not trigger it; gc without --delete prints the maintenance plan
8273
8635
  itself.
8274
8636
  status --watch and every running dev server supervisor also trigger checks
8275
8637
  each minute, so the disk check runs while no command does.
8276
- Size, worktree and sweep checks defer under high load. Attempts back off for at
8638
+ Size, idle, worktree and sweep checks defer under high load. Attempts back off for at
8277
8639
  least one minute. Measured directories are workspace build outputs and Stim's
8278
8640
  shared native, Metro, ccache, Swift compilation and registered caches. The size
8279
8641
  check also records the disk of each owned and parked simulator and emulator
@@ -8282,7 +8644,10 @@ Pressure checks read free disk on the Stim home, projects and worker root
8282
8644
  volumes. On macOS the memory signal is the sysctl pressure level, with no
8283
8645
  signal if sysctl fails. Other platforms use os.freemem(); macOS never falls
8284
8646
  back to it. Memory pressure is recorded only; stopping devices, dev servers
8285
- and helpers is not automatic. While the level is warning or critical, the
8647
+ and helpers for memory is not automatic. The idle check shuts down (never
8648
+ deletes) owned simulators and emulators idle for their workspace's
8649
+ devices.idleShutdownMinutes, also with no supervisor running, through the
8650
+ supervisor's idle rules and records (guide lifecycle budget). While the level is warning or critical, the
8286
8651
  check also names abnormally large processes (guide lifecycle simslim) in
8287
8652
  status's maintenance block and the maintenance log.
8288
8653
 
@@ -8299,13 +8664,28 @@ In on mode a pass runs, cheapest to rebuild first:
8299
8664
  claim (taken for each xcodebuild run that compiles with it) or that
8300
8665
  claim is unresolved; the emptying holds the exclusive claim
8301
8666
  5. linked worktrees whose branch or pull request finished (gc's finished
8302
- worktree rules and gc.worktreeGraceMinutes; maintenance.removeFinishedWorktrees)
8303
- 6. owned simulators and emulators of a finished worktree that is kept, such
8667
+ worktree rules and gc.worktreeGraceMinutes; maintenance.removeFinishedWorktrees),
8668
+ a dirty finished one after archiving its uncommitted files (ARCHIVED
8669
+ CHANGES in stim guide cleanup gc), clean ones with no commits of their
8670
+ own (NO COMMITS OF ITS OWN there), and, with
8671
+ maintenance.removeIdleWorktrees (off by default), in the age
8672
+ sweep clean, pushed ones unused for olderThanDays, as gc --delete
8673
+ --worktrees --older-than removes them, git-ignored files included; never
8674
+ an unfinished dirty one, one with unpushed commits, the source checkout,
8675
+ a locked one, or one whose last use is unknown
8676
+ 6. parked simulators and emulators unused for olderThanDays in the age
8677
+ sweep, through centralized teardown with ownership checked again
8678
+ 7. owned simulators and emulators of a finished worktree that is kept, such
8304
8679
  as a dirty one (not one locked with git worktree lock), once it finished
8305
8680
  and was idle for gc.worktreeGraceMinutes (at least protectRecentHours),
8306
8681
  and of any
8307
8682
  workspace idle for maintenance.deviceIdleDays (3; 0 turns it off) in the
8308
8683
  age sweep. Devices are recreated on the next run.
8684
+ The age sweep runs every maintenance.sweepHours (24) in the background of
8685
+ commands, never at a set hour.
8686
+ maintenance.olderThanDays (7) is its age for steps 2, 3, 5 and 6; with 0 the
8687
+ sweep removes nothing in them by age, while step 1, step 7, finished
8688
+ worktrees and the cap and disk-driven parts of steps 2 and 3 still run.
8309
8689
  Disk-driven steps stop once free space is back above the floor. Kept for
8310
8690
  every pass: a workspace that is in use, holds a lock, was used within
8311
8691
  maintenance.protectRecentHours, is pinned with maintenance.keep, or is the
@@ -8328,14 +8708,21 @@ DISK PRESSURE LEVELS
8328
8708
  level 2, which runs until free disk is back above the floor; level 3 runs
8329
8709
  only while free disk stays under the level 3 floor. At levels 2 and 3 the
8330
8710
  protection window is maintenance.pressureProtectMinutes (10) instead of
8331
- protectRecentHours. Each step re-measures first and stops once free disk is
8332
- back above its level's floor, cheapest to rebuild first.
8711
+ protectRecentHours, except for the build outputs and build directories of
8712
+ level 2, which follow the lightening rule of gc --delete (WORKSPACE BUILD
8713
+ OUTPUTS): a running dev server, a booted or hosted device and a managed
8714
+ tunnel do not keep them, a build or a running macOS app does, and they stay
8715
+ for 15 minutes after the workspace's last ios, android or macos run.
8716
+ Each step re-measures first and stops once free disk is back above its
8717
+ level's floor, cheapest to rebuild first.
8333
8718
  Level 2:
8334
- 1. build outputs of workspaces unused for the short window
8719
+ 1. build outputs of workspaces, with the lightening rule above
8335
8720
  2. parked simulators and emulators, deleted through centralized teardown
8336
- 3. build directories of idle worktrees: ios/build, android/app/build,
8337
- android/build, android/.gradle and .build, at a project root, the
8338
- worktree root, or apps/* and packages/*
8721
+ 3. build directories of worktrees with a Stim workspace, by the same
8722
+ rule: android/app/build, android/build, android/.gradle and .build, at
8723
+ a project root, the worktree root, or apps/* and packages/*. ios/build
8724
+ stays: React Native's pod install writes the codegen output and
8725
+ podspec facades there that only pod install recreates
8339
8726
  4. owned simulators and emulators of workspaces idle for the short window
8340
8727
  5. Stim temp directories, xctrace recordings and SwiftPM temp directories
8341
8728
  in TMPDIR unmodified for 24 hours, with gc --delete's checks (see stim guide cleanup gc)
@@ -8345,12 +8732,14 @@ DISK PRESSURE LEVELS
8345
8732
  and the build
8346
8733
  2. finished worktrees, and clean, pushed worktrees with no commits of
8347
8734
  their own beyond the default branch, with the short window as grace
8348
- period (maintenance.removeFinishedWorktrees still applies)
8735
+ period and idle window (maintenance.removeFinishedWorktrees still applies)
8349
8736
  3. workspaces whose dev server runs but that are otherwise inactive for
8350
8737
  the short window: their dev server stops and their owned simulators
8351
8738
  and emulators are deleted
8352
8739
  4. node_modules and ios/Pods at the root or a project root of idle linked
8353
- worktrees, never the source checkout
8740
+ worktrees, never the source checkout, with the running-process checks
8741
+ of WORKTREE DEPENDENCIES; when worktree warm can restore them they are
8742
+ recorded for the next start, ios or android to restore
8354
8743
  5. idle Gradle and Kotlin daemons, stopped as gc --delete --cache
8355
8744
  gradle-daemons stops them (never during a Stim build, never a busy or
8356
8745
  unverified one, never with STIM_HOME set), only when every Gradle
@@ -8363,8 +8752,11 @@ DISK PRESSURE LEVELS
8363
8752
  still keeps them
8364
8753
  A directory is cleared only when it is a real directory inside its
8365
8754
  worktree, git tracks nothing in it, its worktree is not in use, holds no
8366
- lock or build and is not pinned, and nothing under it, nor the worktree's
8367
- git index, HEAD or Stim state, changed within the short window. A dirty or
8755
+ lock or build and is not pinned, and, for the node_modules and ios/Pods of
8756
+ level 3, nothing under it, nor the worktree's git index, HEAD or Stim
8757
+ state, changed within the short window. A build directory of level 2 also
8758
+ stays in a worktree where no Stim workspace has recorded any state; a build
8759
+ that Stim did not run is not detected. A dirty or
8368
8760
  unpushed worktree can still be stopped and have these directories cleared;
8369
8761
  only its removal is refused. Caches of other tools (pnpm and npm stores,
8370
8762
  CocoaPods, SwiftPM, the shared Gradle caches) are never deleted.
@@ -8401,6 +8793,23 @@ claim fails with ENOSPC or EDQUOT, the pass deletes it and retries once, so a
8401
8793
  pass starts at zero free disk as long as an earlier pass left the reserve; the
8402
8794
  pass writes it again at its end.
8403
8795
 
8796
+ WHAT WAS CLEARED
8797
+ Every run that deletes or changes something appends one record per item to
8798
+ the machine gc log, $STIM_HOME/gc/gc.ndjson: gc --delete with any flags, gc
8799
+ --idle, gc --cache --delete, each maintenance action taken, and what the budget gate
8800
+ reclaims before stim start, ios or android. A dry run writes nothing, and a
8801
+ gc run that only kept items writes nothing. Failed and kept items carry their
8802
+ reason. The file rotates at maintenance.logMaxMb and its old generation is
8803
+ kept for maintenance.logRetentionDays, like the maintenance log.
8804
+ stim logs --source gc the retained records, oldest first
8805
+ stim logs --source gc --json one record per line
8806
+ stim logs --source gc --errors only the items that failed
8807
+ gc works from any directory because the log is machine-wide; it cannot be
8808
+ combined with other --source values and --source all leaves it out. Fields
8809
+ (\`guide logs\`, GC LOG): runId, trigger (manual, desktop, maintenance or
8810
+ budget), options, kind, status (done, kept or failed), label, id, path,
8811
+ bytes, detail.
8812
+
8404
8813
  CLEANUP AND DISK
8405
8814
 
8406
8815
  worktree remove stops the agent-device daemon when the workspace's
@@ -8478,6 +8887,11 @@ this cleanup. agent-device is optional: a missing binary skips this step, and a
8478
8887
  failed or timed-out list/close prints a device line on stderr while teardown
8479
8888
  continues. Each cleanup pass allows at most 15 seconds of agent-device calls
8480
8889
  per device; the pass \`stop\` makes after shutdown has its own budget.
8890
+ A daemon agent-device will not replace (daemon-busy: it is starting up or still
8891
+ has sessions, requests or leases) is not retried. Stim prints one line per
8892
+ device, naming each busy daemon, even though it lists sessions before and after
8893
+ shutdown: sessions on the device in them stay open until the daemon is idle or,
8894
+ if it is yours, stopped with agent-device daemon stop --state-dir <dir>.
8481
8895
  A local daemon that cannot use socket transport or a CLI without the target guard
8482
8896
  skips cleanup with a warning; no remote daemon is used.
8483
8897
 
@@ -8668,7 +9082,10 @@ LINKED WORKTREES
8668
9082
  still exists, as after a removal killed between the rename and git, stays
8669
9083
  with the reason; if that worktree is the one the entry came from, run
8670
9084
  \`stim worktree remove\` on it to finish the removal.
8671
- gc never moves the directories back.
9085
+ gc never moves the directories back. Warm uses the same trash for the
9086
+ known paths it deletes from a copy (such as a copied .pnpm-store); gc
9087
+ --delete deletes such an entry (trash.json kind warm) once its claim is free,
9088
+ whatever the state of the worktree.
8672
9089
  When git still refuses the removal, the kept ownership record no longer
8673
9090
  names the devices that were parked or deleted. A workspace that is in use
8674
9091
  (see IN USE below) keeps its directory, devices and registry entry, and the
@@ -8677,11 +9094,14 @@ LINKED WORKTREES
8677
9094
  only when it has no unique commits.
8678
9095
 
8679
9096
  IN USE
8680
- gc and worktree remove never delete a workspace's directory, build outputs
8681
- or checkout while it is in use: its dev server supervisor is running or
9097
+ gc and worktree remove never delete a workspace's directory or checkout
9098
+ while it is in use: its dev server supervisor is running or
8682
9099
  cannot be verified, a \`stim ios\`, \`stim android\` or \`stim stop\` run
8683
9100
  holds its native-run.lock, a live or unresolvable build lock or slot names it,
8684
- or its managed tunnel or managed remote lock is held. The deletion holds
9101
+ or its managed tunnel or managed remote lock is held. Build outputs follow
9102
+ the narrower rule in WORKSPACE BUILD OUTPUTS (\`guide cleanup disk\`): only
9103
+ a build, a running macOS app, or an ios, android or macos run in the last
9104
+ 15 minutes keeps them. The deletion holds
8685
9105
  native-run.lock itself, so no native run can start partway through. A lock
8686
9106
  that names no workspace blocks nothing, because every build also holds its
8687
9107
  own workspace's native-run.lock; gc lists it with the command that removes
@@ -8698,14 +9118,17 @@ SWEEPING FINISHED WORKTREES
8698
9118
  ever ran in it. It groups them by git worktree and reports each linked
8699
9119
  worktree with the reason it is removed or kept. A worktree is finished when
8700
9120
  its branch is merged into the default branch, or its pull request was
8701
- merged or closed (PULL REQUESTS below). \`--worktrees\` also removes
9121
+ merged or closed (PULL REQUESTS below). A clean worktree with no commits
9122
+ of its own counts as finished once idle (NO COMMITS OF ITS OWN below).
9123
+ \`--worktrees\` also removes
8702
9124
  one that is idle: no recorded use for --older-than days, 7 without it; a
8703
9125
  worktree whose last use is unknown is kept, unless it has no commits of its
8704
- own beyond the default branch (NO COMMITS OF ITS OWN below). Both need the
8705
- same clean state;
9126
+ own beyond the default branch (NO COMMITS OF ITS OWN below). Each needs a
9127
+ clean state, except a finished one whose changes are archived;
8706
9128
  a worktree is kept when it is the source checkout, bare, locked, holds
8707
9129
  another worktree (worktree remove refuses it too), in use,
8708
- dirty (untracked files count; pod install churn and
9130
+ dirty and not finished, or dirty and finished when its changes cannot be
9131
+ archived (ARCHIVED CHANGES below; untracked files count; pod install churn and
8709
9132
  watchman's untracked \`.watchman-cookie-*\` files alone do not), unpushed
8710
9133
  (commits no remote-tracking ref or other local branch reaches), or has
8711
9134
  initialized submodules. Without --worktrees, the report leaves out the
@@ -8736,14 +9159,17 @@ SWEEPING FINISHED WORKTREES
8736
9159
  an unexpired device lease. When the simulator or emulator list cannot be
8737
9160
  read, the worktree counts as in use. A device left booted keeps the
8738
9161
  worktree until \`stim stop\` in it, or \`gc --idle <duration>\`, shuts
8739
- the device down.
9162
+ the device down. A worktree that holds the running stim or the realpath of
9163
+ the \`stim\` on PATH is in use too, and \`stim worktree remove\` refuses it
9164
+ even with --force, as it refuses an installed stim-server service's checkout.
8740
9165
 
8741
9166
  RECENT ACTIVITY keeps a worktree gc would otherwise remove until
8742
9167
  gc.worktreeGraceMinutes (120 by default, \`guide settings\`) have passed
8743
9168
  since the latest of: a write to its git index, HEAD or HEAD reflog, a write
8744
9169
  to its Stim workspace state or log files, and, for a merged branch, the
8745
9170
  committer date of the commit that brought it into the default branch, or
8746
- the time its pull request was merged or closed. That
9171
+ the time its pull request was merged or closed, and, for a dirty finished
9172
+ worktree, the newest change to its uncommitted or untracked files. That
8747
9173
  window is when an agent that just merged runs \`stim stop\` and \`stim
8748
9174
  worktree remove\` itself. The reason is recent-activity, and the report
8749
9175
  and the JSON eligibleAt field say when it becomes removable. When that
@@ -8798,18 +9224,56 @@ SWEEPING FINISHED WORKTREES
8798
9224
 
8799
9225
  NO COMMITS OF ITS OWN: a worktree whose HEAD the default branch contains
8800
9226
  and whose branch has no commit made on it (fresh, cut from another
8801
- branch, or reset onto one; the MERGED rule above) holds nothing git would
8802
- lose, but looks the same as one just made by git worktree add. Plain gc keeps
8803
- it (reason not-merged, "no commits of its own beyond origin/<default>").
8804
- \`--worktrees\` removes it when idle: by its recorded use when there is
8805
- one, else by the RECENT ACTIVITY sources above being older than
8806
- --older-than days (reason recently-used while they are newer, and
8807
- last-use-unknown when they cannot be read). Automatic maintenance removes it
8808
- only at disk pressure level 3, once that activity is older than
8809
- maintenance.pressureProtectMinutes and at least 5 minutes old, whatever
8810
- the setting; unreadable activity keeps it. Removal re-checks, under the removal
8811
- locks, that HEAD did not move and the worktree is still idle, and
8812
- \`stim worktree remove\` still refuses dirty or unpushed work.
9227
+ branch, reset onto one, or a detached HEAD; the MERGED rule above) holds
9228
+ nothing git would lose, but looks the same as one just made by git
9229
+ worktree add. gc --delete and the maintenance worktree check remove a clean
9230
+ one without an open pull request once the RECENT ACTIVITY sources above
9231
+ are older than gc.worktreeGraceMinutes and maintenance.protectRecentHours
9232
+ (2 hours by default), and never younger than 5 minutes (reason
9233
+ recent-activity until then, activity-unknown when they cannot be read). At
9234
+ disk pressure level 3 the window is maintenance.pressureProtectMinutes,
9235
+ still at least 5 minutes. Removal re-checks, under the removal locks, that
9236
+ HEAD did not move and the worktree is still idle, and \`stim worktree
9237
+ remove\` still refuses dirty or unpushed work. A process Stim does not
9238
+ track, such as a benchmark run from a detached build, does not keep it.
9239
+
9240
+ ARCHIVED CHANGES: a dirty worktree that is otherwise finished, idle past
9241
+ RECENT ACTIVITY and not in use is archived, then removed, by gc --delete
9242
+ and the maintenance worktree check. Under the removal locks Stim writes
9243
+ \`git diff --binary HEAD\` (staged and unstaged changes together) to
9244
+ tracked.patch, the untracked files git does not ignore to untracked.tar,
9245
+ HEAD's commits the default branch lacks to head.bundle,
9246
+ with a manifest.json naming the branch, HEAD, pull request and files, in
9247
+ $STIM_HOME/archive/worktrees/<repository>/<branch-or-dir>-<timestamp>/.
9248
+ Ignored files (node_modules, build output) are not saved. It verifies the
9249
+ patch with \`git apply --check -R\`, the tarball's file list, and that no
9250
+ archived file changed, again just before git removes the worktree; any
9251
+ failure keeps the worktree and deletes the archive. It also keeps a
9252
+ worktree whose archive.enabled is false, or false for a project registered
9253
+ inside it, with archive.maxAgeDays or
9254
+ archive.maxTotalGb 0, whose untracked files or patch exceed
9255
+ archive.maxTotalGb, whose archive would leave less free disk than
9256
+ budget.hardFloorDiskGb, whose staged version of a file differs from its
9257
+ working tree, or that holds an untracked nested repository; disk
9258
+ pressure level 3 never archives. Archives are 0700 and 0600 with no
9259
+ redaction, so an untracked secret such as a .env file that git does not
9260
+ ignore is saved; use archive.enabled false for a sensitive repository. A
9261
+ \`git status\` or editor refresh that rewrites the index restarts RECENT
9262
+ ACTIVITY. The removal line and the gc
9263
+ and maintenance logs name the archive and the restore commands; head.bundle
9264
+ exists only when the default branch lacks HEAD, and lets the restore work
9265
+ after the branch is deleted:
9266
+ git -C <repository> bundle unbundle <archive>/head.bundle (with a bundle)
9267
+ git -C <repository> worktree add --detach <path> <HEAD>
9268
+ git -C <path> apply <archive>/tracked.patch
9269
+ tar -xf <archive>/untracked.tar -C <path>
9270
+ Archive retention, which runs before and after each new worktree archive
9271
+ and after each removal that archives a workspace, deletes worktree archives
9272
+ older than archive.maxAgeDays. A new archive that would not fit beside the
9273
+ others in archive.maxTotalGb keeps its worktree instead, so the oldest are
9274
+ deleted for size only after that cap is lowered. An archive whose removal
9275
+ is still running holds an ownership claim and is never deleted. Plain \`stim worktree remove\` never archives; it
9276
+ refuses dirty work as before.
8813
9277
 
8814
9278
  PULL REQUESTS: with fresh state, gc runs one \`gh api graphql\` query per
8815
9279
  repository that asks for the 20 newest pull requests of each linked worktree's branch, as
@@ -9201,9 +9665,10 @@ THE ONE CASE GC WILL NOT REAP
9201
9665
  summary: "disk usage, workspace build outputs, logs and device recordings, AVD and build-log sizes, the data partition, trimming the shared caches",
9202
9666
  body: () => `DISK
9203
9667
  Logs, state, pidfiles and Xcode DerivedData are under the global workspace
9204
- directory, and \`worktree remove\` reclaims them. \`gc --delete\` clears the
9205
- build outputs of workspaces nobody is using (WORKSPACE BUILD OUTPUTS
9206
- below) and keeps the rest. Gradle retains its normal
9668
+ directory, and \`worktree remove\` reclaims them. \`gc --delete\` lightens
9669
+ every workspace that is not building and did not run a native command in
9670
+ the last 15 minutes, clearing its build outputs (WORKSPACE BUILD OUTPUTS
9671
+ below), and keeps the rest. Gradle retains its normal
9207
9672
  project build directories while sharing task outputs through its build cache.
9208
9673
 
9209
9674
  Android AVDs normally live under ~/.android/avd, and a booted owned AVD can
@@ -9254,18 +9719,35 @@ a changed value.
9254
9719
  WORKSPACE BUILD OUTPUTS
9255
9720
  Each workspace directory holds derived-data/, gradle-build/, android-cas/,
9256
9721
  cache-provider/, macos/build, the staged macos/<Product>.app and macos/staging-*
9257
- leftovers. \`gc\` reports them as one detected cache, "Workspace build
9258
- outputs", with a per-workspace size, last use and verdict. Plain
9259
- \`gc --delete\` clears them for every workspace not in use (see \`guide
9260
- cleanup gc\`), before anything else. \`--older-than <days>\` limits that to
9722
+ leftovers. The project's own android/app/build, android/build,
9723
+ android/.gradle and .build count with them when git ignores every file in
9724
+ the directory and tracks none; one that fails that check, or is not in a
9725
+ git checkout, stays, and \`gc --delete\` prints why when the check fails
9726
+ between the report and the deletion. ios/build stays: React Native's pod
9727
+ install writes podspec facades and codegen output there that only pod
9728
+ install recreates. \`gc\` reports them as one
9729
+ detected cache, "Workspace build outputs", with a per-workspace size, last
9730
+ use and verdict. Plain \`gc --delete\` lightens every workspace, before
9731
+ anything else, by clearing them, and prints the bytes freed per workspace.
9732
+ A running dev server, a booted or hosted simulator or emulator and a managed
9733
+ tunnel no longer keep them. They stay while the workspace is building (its
9734
+ native-run.lock is held, a live or unresolvable build lock or slot names
9735
+ it, or its build claim is live), while its macOS app or build runs or cannot
9736
+ be verified, and for 15 minutes after its last ios, android or macos run,
9737
+ cache hits included, as recorded in state.json's build records. Dev server
9738
+ starts, reloads and logs do not count as a native run. The shared build
9739
+ cache, the Xcode compilation cache, the Gradle caches and the Metro cache
9740
+ are never touched. \`--older-than <days>\` limits that to
9261
9741
  workspaces idle at least that long, and keeps one whose last use is
9262
9742
  unknown. \`--cache workspaces\` acts on them alone, except in the workspace
9263
9743
  directories that plain \`gc --delete\` removes whole (dead projects and
9264
9744
  orphaned directories); \`--cache all\` includes them. macos/ itself,
9265
9745
  macos/runtime.lock and its claim set, workspace.json, state.json, logs/,
9266
- other macOS files, locks and device records stay, as does the project's
9267
- own .build. The workspace keeps its devices and ports. A running, building,
9268
- unverified or hosted macOS app keeps its workspace untouched.
9746
+ other macOS files, locks and device records stay. The workspace keeps its
9747
+ devices, ports and running dev server. Automatic maintenance at disk
9748
+ pressure level 2 clears them by the same rule (DISK PRESSURE LEVELS); level
9749
+ 1 keeps its own rules. A running, building, unverified or hosted macOS app
9750
+ keeps its workspace untouched.
9269
9751
  stim gc --delete --cache workspaces --older-than 7
9270
9752
  Last use is the newest of the lastUsedAt that start, ios, android, reload
9271
9753
  and worktree warm record in state.json, lastBuild.startedAt,
@@ -9282,6 +9764,30 @@ WORKSPACE BUILD OUTPUTS
9282
9764
  build recompiles Swift. The next \`stim macos\` rebuilds its scratch from
9283
9765
  nothing (a full Swift build) and restages the app.
9284
9766
 
9767
+ WORKTREE DEPENDENCIES
9768
+ Plain \`gc --delete\` also lightens linked worktrees: it deletes the
9769
+ node_modules and ios/Pods at the root or a project root of a linked
9770
+ worktree that has a Stim workspace, never the source checkout. A worktree
9771
+ keeps them while it is in use (its dev server runs, a native run or build
9772
+ holds it), while it was used in the last 15 minutes (a git index, HEAD or
9773
+ reflog write, a Stim state or log write, or a write at the top of the
9774
+ directory), while git tracks a file in them, while a stim-server service
9775
+ or the stim on PATH runs from it, while a live process has its command
9776
+ line or working directory in it, and when the running processes cannot be
9777
+ listed (always on Windows). It also keeps them when \`stim worktree warm\`
9778
+ could not put the same thing back: warm would not copy that directory from
9779
+ the source checkout (it is missing there, excluded by .worktreeexclude or
9780
+ worktree.exclude, or inside a larger ignored directory such as a wholly
9781
+ ignored ios/), the worktree's lockfile (or ios/Podfile.lock for Pods)
9782
+ differs from the source checkout's, or the source checkout's own copy was
9783
+ not installed from its lockfile. gc records what it deleted in the
9784
+ workspace's lightened.json; the next \`stim start\`, \`stim ios\` or
9785
+ \`stim android\` there runs \`stim worktree warm\` first to restore it, and
9786
+ refuses with STIM_DEPS_FAILED, keeping the record, when those checks no
9787
+ longer pass, when warm fails, or when it leaves a directory missing. A
9788
+ deletion that stopped part-way is finished before the warm. Other commands, such as a test run, find the directory missing
9789
+ until then. .pnpm-store stays: it is all clones and frees nothing.
9790
+
9285
9791
  WORKSPACE LOGS
9286
9792
  \`gc\` reports the size of each workspace's logs/ (the workspaceLogs
9287
9793
  section of \`gc --json\`). metro.ndjson, client.ndjson and device.ndjson and
@@ -9341,6 +9847,12 @@ SHARED BUILD CACHES
9341
9847
  report-only because every Gradle build shares it. Stim reports its size
9342
9848
  but never prunes or empties it, including with --older-than or --cache all.
9343
9849
 
9850
+ A cache directory that \`gc --delete --older-than\` cannot list (EACCES,
9851
+ EPERM or ELOOP, for example) is a failed trim, not an empty one: the run
9852
+ prints "N entries in <dir> could not be read or removed", records the cache
9853
+ as failed and exits 1. Fix the directory's permissions and run it again. A
9854
+ directory that is missing or is not a directory is not a failure.
9855
+
9344
9856
  Trim rather than empty. Emptying costs the next build in every project the
9345
9857
  time the cache was saving.`
9346
9858
  }
@@ -9461,12 +9973,16 @@ KEYS STIM READS
9461
9973
  embedded JS, no Metro, cache keyed -release-sim, and
9462
9974
  a JS-bundle swap on cache hits. The \`--configuration\`
9463
9975
  flag overrides this per invocation. Unset means Debug.
9464
- ios.remote "proxy", "eas", or a named approved Mac from
9465
- remote.machines, with the same meaning as --remote.
9466
- "auto" places on an approved Mac when this machine is full or
9467
- busy. Unset runs here; "local" runs here even when a
9468
- lower layer says otherwise. A --device run ignores
9469
- it. See lifecycle hosted-ios.
9976
+ ios.remote where the simulator runs, with the same meaning as
9977
+ --remote: "local" here, even when a lower layer says
9978
+ otherwise; "auto" here, and on an approved Mac from
9979
+ remote.machines when this machine is full or busy;
9980
+ "remote" on the first approved Mac in remote.machines
9981
+ order with room, here only when none takes it;
9982
+ "remote-only" the same but never here; a named
9983
+ approved Mac exactly that Mac; "proxy" or "eas" a
9984
+ remote device backend. Unset runs here. A --device run
9985
+ ignores it. See lifecycle hosted-ios.
9470
9986
  ios.projectPath directory under the app holding a bare React Native
9471
9987
  app's Xcode project and Podfile; default "ios". "."
9472
9988
  is the app directory itself (RNTester's layout).
@@ -9495,9 +10011,9 @@ KEYS STIM READS
9495
10011
  iOS work after reviewing service tradeoffs; see
9496
10012
  \`stim guide lifecycle simslim\`. Each local \`stim ios\`
9497
10013
  reconciles the profile on its Stim-owned simulator.
9498
- The first change can reboot it; a matching profile is a
9499
- fast no-op. Removing the setting restores stock services
9500
- when Stim applied the profile. Absolute paths, root or
10014
+ The first change can reboot it; an unchanged profile on
10015
+ the same boot is skipped. Removing the setting
10016
+ restores stock services when Stim applied the profile. Absolute paths, root or
9501
10017
  symlink escapes, and missing files are refused before
9502
10018
  simulator creation. Remote and unowned simulators are
9503
10019
  never changed.
@@ -9687,14 +10203,13 @@ ${ANDROID_AVD_CONFIG_HELP.map((line) => ` ${line}`).joi
9687
10203
  password, handed to apksigner through its
9688
10204
  environment, never its command line. Unset means
9689
10205
  the debug keystore's fixed "android".
9690
- android.remote "proxy", "eas", or a named approved Mac in
9691
- remote.machines; "auto" places on an approved Mac when
9692
- this machine is full or busy. Unset runs here; "local"
9693
- runs here even when a lower layer says otherwise.
9694
- A --device run ignores it. See lifecycle
9695
- hosted-android.
9696
- remote.easFallback true lets "auto" (ios.remote, android.remote or
9697
- --remote auto) run the simulator or emulator on a
10206
+ android.remote where the emulator runs, with the same words as
10207
+ ios.remote: "local", "auto", "remote", "remote-only",
10208
+ a named approved Mac in remote.machines, "proxy" or
10209
+ "eas". Unset runs here. A --device run ignores it. See
10210
+ lifecycle hosted-android.
10211
+ remote.easFallback true lets "auto" or "remote" (ios.remote, android.remote
10212
+ or --remote) run the simulator or emulator on a
9698
10213
  billed EAS Simulator when this machine is at its
9699
10214
  concurrency.maxDevices cap (or has runs queued) and no
9700
10215
  remote.machines entry takes the run, instead of waiting
@@ -9828,6 +10343,16 @@ ${ANDROID_AVD_CONFIG_HELP.map((line) => ` ${line}`).joi
9828
10343
  compiled into Contents/Resources before signing
9829
10344
  macos.resources map of Contents/Resources destinations to source paths
9830
10345
  relative to Package.swift; see stim guide macos
10346
+ macos.remote where stim macos launches the app, with the words of
10347
+ ios.remote except "eas" and "proxy": "local" here;
10348
+ "auto" here unless this machine is busy, then an
10349
+ approved Mac in remote.machines; "remote" on the
10350
+ first approved Mac in order with room, here only when
10351
+ none takes it; "remote-only" the same but never here,
10352
+ which keeps the app from taking focus on this Mac; a
10353
+ named Mac exactly that Mac. The --remote flag
10354
+ overrides it. A recorded hosted app keeps its Mac
10355
+ until stim stop. Unset runs here. See stim guide macos.
9831
10356
  web.url the page \`stim web\` opens in the owned Chrome, an
9832
10357
  http:// or https:// URL. {port:<label>} becomes the
9833
10358
  workspace's named port (allocated like \`stim ports
@@ -9885,8 +10410,9 @@ ${ANDROID_AVD_CONFIG_HELP.map((line) => ` ${line}`).joi
9885
10410
  them from the environment or the machine layers.
9886
10411
 
9887
10412
  Each setting takes its documented type: string, array of strings, number,
9888
- boolean, or object. ios.remote and android.remote accept backend names or
9889
- tailnet machine names. metro.tunnel, web.viewport,
10413
+ boolean, or object. ios.remote, android.remote, macos.remote and remote.build
10414
+ accept placement words (local, auto, remote, remote-only) or tailnet machine
10415
+ names, and ios.remote and android.remote also the backend names. metro.tunnel, web.viewport,
9890
10416
  optimizations.android.compilerCache and optimizations.android.pch take only
9891
10417
  their listed choices. A value of the wrong
9892
10418
  type or outside those choices is refused by name on every command that resolves
@@ -10088,6 +10614,11 @@ never decide who the worker is. With --fix, a pending entry is replaced when
10088
10614
  STIM_ACCESS_TICKET names another ticket.
10089
10615
  Approved doctor entries under remoteMachines and deviceHosts include host
10090
10616
  { name, screenRecording, accessibility } when the worker reports it.
10617
+ A remote machine doctor cannot use carries a diagnosis { layer, code, title,
10618
+ message, remedy } naming the first layer that failed: Tailscale on this machine,
10619
+ the peer, the serve route, stim-server, or compatibility. Tell the user its
10620
+ message and remedy; the remedies run on the remote machine or in Tailscale, so
10621
+ agents do not run them. \`stim guide facts doctor\` lists the codes.
10091
10622
 
10092
10623
  REMOTE MACHINES AND PHONE APP
10093
10624
  When the user mentions slow builds, a busy machine, a full disk or device limit, or
@@ -10107,6 +10638,21 @@ simulator sessions on, by MagicDNS name with an optional serve port (default
10107
10638
  stim settings set remote.machines '["janics-mac-mini"]'
10108
10639
  stim doctor --fix
10109
10640
 
10641
+ The order of \`remote.machines\` is the priority order for every workload: the
10642
+ build, simulator, emulator and macOS app each go to the first listed machine that
10643
+ has approval, a free slot and a 5-minute load per core under that machine's own
10644
+ \`server.maxLoadPerCore\`, and the next one in order only when it does not. A
10645
+ machine at its caps, over its load limit, unreachable or lacking approval is
10646
+ skipped; load does not rank the machines that qualify, so the first one fills
10647
+ before the next is used. Put the fastest machine first and set the order with
10648
+ one command; \`stim doctor\` prints it as \`placement order\` and gives each
10649
+ machine a \`priority\` in its JSON:
10650
+
10651
+ stim settings set remote.machines '["fast-mini","slow-mini"]'
10652
+
10653
+ A host reports its own load limit in its device offer; a stim-server older than
10654
+ that does not, and this machine's \`server.maxLoadPerCore\` stands in for it.
10655
+
10110
10656
  Automatic membership is separate from approval. In Desktop Settings > Remote
10111
10657
  Machines, use Builds enabled and Simulators enabled for this machine or a
10112
10658
  configured remote. The equivalent machine settings list the excluded members;
@@ -10194,9 +10740,9 @@ An invalid remote.machines setting reports an error and preserves every saved cr
10194
10740
  replaced machine, run it, then re-add the name to request a new approval.
10195
10741
  A definite revoked or lapsed request can be requested again with --fix.
10196
10742
  An in-progress approval inspection reports busy rather than replacing its
10197
- pending token. \`stim macos --remote <machine>\` runs a macOS app on an
10198
- approved remote Mac (stim guide macos); \`ios\` and \`android\` do not yet
10199
- place sessions on these Macs. To view or control a hosted macOS app, a person
10743
+ pending token. \`stim macos --remote <placement>\` or \`macos.remote\` runs a macOS
10744
+ app on an approved remote Mac (stim guide macos), and \`ios.remote\` and
10745
+ \`android.remote\` place simulators and emulators on them. To view or control a hosted macOS app, a person
10200
10746
  on that Mac approves Screen & System Audio Recording and Device Control and Data
10201
10747
  Access (Accessibility on macOS 26 and earlier) for Stim Host, the app
10202
10748
  \`stim-server service install\` runs the server under.
@@ -10243,18 +10789,35 @@ builds, or the build messages change; their Stim builds can differ. A
10243
10789
  stim-server too old to report one needs the same Stim build as this machine.
10244
10790
 
10245
10791
  \`remote.build\` (machine scope, default auto) selects placement for
10246
- \`ios\`, \`android\` and \`macos\`. \`--remote-build <auto|local|name>\`
10792
+ \`ios\`, \`android\` and \`macos\`. \`--remote-build <local|auto|remote|remote-only|name>\`
10247
10793
  overrides STIM_REMOTE_BUILD, which overrides the setting. A blank environment
10248
- value is unset. Trimmed auto/local are case-insensitive. Machine names match
10794
+ value is unset. \`doctor\` flags an invalid value in the machine config or in
10795
+ STIM_REMOTE_BUILD before a build refuses on it. Trimmed placement words are case-insensitive. Machine names match
10249
10796
  configured names case-insensitively, with port 7443 when omitted; reports use
10250
10797
  the configured entry.
10251
10798
 
10252
- auto follows remote.buildMode, considering only enabled automatic pool members
10253
- local builds only on this machine for this invocation
10254
- name requires a matching entry in remote.machines, already paired and
10255
- approved for builds; ignores remote.buildMode and this machine's load/slot gating
10799
+ local builds only on this machine for this invocation
10800
+ auto (default) here while this machine has capacity, a remote
10801
+ machine from remote.machines when it does not; only enabled
10802
+ automatic pool members count
10803
+ remote on the first remote machine in remote.machines order that
10804
+ accepts the build, here only when none does
10805
+ remote-only the same, never here: when no remote machine accepts the build
10806
+ it refuses with STIM_OFFLOAD_REFUSED
10807
+ name requires a matching entry in remote.machines, already paired and
10808
+ approved for builds; ignores this machine's load/slot gating
10809
+
10810
+ \`remote.buildMode\` and STIM_REMOTE_BUILD_MODE were removed and are no longer read.
10811
+ \`doctor\` and \`stim settings\` report any left in a config file or the environment,
10812
+ \`stim settings set remote.buildMode\` refuses with the \`remote.build\` command that
10813
+ replaces it (auto stays auto, force becomes remote, off becomes local), and
10814
+ \`stim settings unset remote.buildMode\` removes the leftover key from every layer
10815
+ that has it (--scope limits it to one).
10256
10816
 
10257
10817
  A name never prompts for pairing, tries another remote machine or falls back here.
10818
+ remote-only refuses instead of building here for a build that cannot leave this
10819
+ machine (device, Release, an unknown simulator runtime, the build cache off), and
10820
+ when no pairing or approval exists.
10258
10821
  Any refusal or failure is STIM_OFFLOAD_REFUSED with the remote machine and reason.
10259
10822
  Device, Release/non-Debug, --remote, Android CAS compiler builds, builds with
10260
10823
  the build cache off, and an unknown simulator runtime cannot build there and
@@ -10263,7 +10826,8 @@ at setup before consulting the cache; they create no build record or failed-run
10263
10826
  stats. A listed paired name with a cache hit compiles nothing, contacts no worker
10264
10827
  and records buildMachine with builtOn absent. Build failures retain the selected
10265
10828
  value; builtOn is here or the configured machine entry when compilation ran.
10266
- Prebuild and pod install still run on this machine before a named offload. A refusal
10829
+ Prebuild and pod install still run on this machine before a named offload; stim ios
10830
+ checks the machine's toolchain first. A refusal
10267
10831
  starts no local xcodebuild, Gradle or SwiftPM compile.
10268
10832
  A React Native offload sends the tracked and untracked files, and the gitignored
10269
10833
  files the native fingerprint hashes or the Expo config reads, such as .env.local
@@ -10280,26 +10844,29 @@ Copyable agent prompt:
10280
10844
  Run stim ios --remote-build janics-mac-mini. If it refuses, report the
10281
10845
  STIM_OFFLOAD_REFUSED reason and remedy; do not retry with another placement.
10282
10846
 
10283
- With remote.build auto, \`remote.buildMode\` decides where an iOS simulator Debug build or an Android
10284
- emulator debug build or a stim macos SwiftPM Debug build compiles:
10847
+ Which remote machine takes a build that leaves this machine follows the order of
10848
+ \`remote.machines\`: the first machine that accepts it, meaning it has a free slot
10849
+ (\`concurrency.maxBuilds\`), enough disk, a matching toolchain, and a load per core
10850
+ under its own \`server.maxLoadPerCore\`. A machine past its limit is skipped like a
10851
+ full one and the next in order is tried; the first machine fills to its caps before
10852
+ the next one gets a build. A machine that already holds the exact completed
10853
+ artifact goes ahead of the order, since that needs no build. With \`auto\` the
10854
+ build leaves this machine only when it is busy:
10285
10855
 
10286
10856
  auto (default) here while this machine has capacity: a free
10287
10857
  \`concurrency.maxBuilds\` slot (always, with no build limit) and a
10288
- load per core under \`server.maxLoadPerCore\`. Otherwise on a
10289
- remote machine that accepts the build and is expected to be faster:
10290
- while every slot here is busy, any remote machine that accepts; while only
10291
- the load is high, one whose load per core is lower than this
10292
- machine's. A machine too old to report its load counts only while every
10293
- slot here is busy.
10294
- force on a remote machine whenever one accepts it
10295
- off here when local remains enabled in the automatic build pool
10296
-
10297
- For React Native and Expo, among machines that can take a new build, a matching dependency installation
10298
- ranks before load. The worker checks the requested project, lockfile, install
10299
- arguments and the major of the Node the app pins, and verifies that its modules
10300
- and install marker remain present.
10301
- Older workers' dependency hints and mere checkout or build-folder presence do
10302
- not influence this ranking. Exact completed-artifact hits still come first.
10858
+ load per core under \`server.maxLoadPerCore\`. Otherwise on the first
10859
+ remote machine in order that accepts the build. A machine too old to
10860
+ report its load counts only while every slot here is busy.
10861
+ remote on the first remote machine in order that accepts the build, whatever
10862
+ load this machine is under; here only when none does
10863
+ remote-only as remote, but when none does the build refuses
10864
+
10865
+ A worker that holds a matching dependency installation does not rank ahead of an
10866
+ earlier machine in the order; it only saves the install on the machine that gets
10867
+ the build. The worker checks the requested project, lockfile, install arguments
10868
+ and the major of the Node the app pins, and verifies that its modules and install
10869
+ marker remain present.
10303
10870
 
10304
10871
  The worker installs dependencies with the lockfile nearest the app: the first
10305
10872
  pnpm-lock.yaml, yarn.lock or package-lock.json in its directory or above it in
@@ -10324,10 +10891,11 @@ Load per core is the 5-minute load average divided by the CPU count; a machine's
10324
10891
  native builds are its Stim runs in prebuild, pods or compile on that machine, not
10325
10892
  the ones it offloaded.
10326
10893
  \`server.maxLoadPerCore\` (default 2) is also the busy threshold for
10327
- automatic iOS and Android device placement. It is the load per core at which a machine
10328
- counts as saturated, both here and on a remote machine.
10894
+ automatic iOS, Android and macOS placement. It is the load per core at which a
10895
+ machine counts as saturated: each machine applies its own value, and a remote
10896
+ machine past it is skipped like a full one.
10329
10897
 
10330
- STIM_REMOTE_BUILD_MODE overrides it for one command. Device, Release and
10898
+ Device, Release and
10331
10899
  \`--remote eas|proxy\` builds, Android builds with the Apple Clang CAS compiler cache,
10332
10900
  and iOS/Android runs with the build cache off, always build here. Hosted iOS
10333
10901
  Debug builds can use a separate --remote-build, targeting the hosting Mac's
@@ -10340,7 +10908,7 @@ verified compile; remote output is not published under an unverified client key.
10340
10908
  An offloaded iOS/Android build first runs prebuild (and \`pod install\` for iOS) here,
10341
10909
  then asks every paired machine what it can build. It takes one whose Stim
10342
10910
  build matches this Mac and, for iOS and macOS, CPU matches the target simulator, with enough disk, that does not decline,
10343
- preferring the one that already holds this repository, then the least loaded,
10911
+ taking the first in remote.machines order that qualifies,
10344
10912
  and moves to the next one in that order when a machine that offered fails the
10345
10913
  sync or refuses to start the build.
10346
10914
  After a local and configured-provider miss, native Xcode simulator builds can
@@ -10360,18 +10928,44 @@ match. Completed archives survive worker checkout changes or removal; hits
10360
10928
  need no source upload or build slot. Disabled remote artifact reads skip this
10361
10929
  lookup. Release and physical-device builds keep their existing paths.
10362
10930
  SwiftPM, compiler and dependency caches use their existing paths.
10363
- For iOS the machine's Xcode and simulator SDK must match, and it needs an
10364
- iPhone simulator on the target runtime. Its project-selected CocoaPods must match too, unless
10365
- the app's Gemfile.lock, in ios/ or the project root, pins CocoaPods: both machines then run that version through
10366
- bundler, so the machine needs only Bundler on its stim-server PATH and
10931
+ For iOS the machine's Xcode and simulator SDK must match. Its offer reports the
10932
+ developerDir xcode-select prints and the Xcode apps in /Applications (version,
10933
+ build, and license and first-launch checks while no Xcode works), so doctor's
10934
+ fix for no usable Xcode is the exact command to run there: sudo xcode-select -s
10935
+ <the Xcode with this machine's build, else the newest>/Contents/Developer when
10936
+ the Command Line Tools or another Xcode is selected, sudo xcodebuild -license
10937
+ accept and sudo xcodebuild -runFirstLaunch for the checks that fail, or xcodes
10938
+ install <version> with no Xcode app. A runtime the machine lacks gets
10939
+ xcodebuild -downloadPlatform iOS -buildVersion <version>, or a pointer to the
10940
+ Xcode problem when xcode-select is the cause. A stim-server that reports no
10941
+ developerDir keeps the prose fix. It also needs an
10942
+ iPhone simulator on the target runtime. A missing or different global CocoaPods
10943
+ keeps auto, remote and remote-only placement from choosing the machine for a project that does not
10944
+ pin it, unless the machine already holds the exact artifact; the placement line
10945
+ says why and doctor shows the install command. A named --remote-build still
10946
+ sends the build, and a pod install that fails there reports "pod install failed"
10947
+ with the fix on that machine. When the app's Gemfile.lock, in ios/ or the project
10948
+ root, pins CocoaPods, both machines run that version through
10949
+ bundler, so the machine needs Bundler on its stim-server PATH and
10367
10950
  installs the pinned gems itself on the first build. This Mac's side of the
10368
10951
  comparison runs \`pod --version\` in the directory pod install runs in. The comparison selects
10369
10952
  the app's .ruby-version when installed, otherwise rvm's or rbenv's default Ruby, and only when neither exists the GEM_HOME, GEM_PATH and Ruby-related PATH entries of the machine's login shell (read once with $SHELL -lic), unless the caller already sets GEM_HOME, with pod install's UTF-8 locale defaults. pod install, including the Bundler run, uses the same environment.
10370
10953
  For Android its CPU may differ, since the APK's ABIs come from the NDK, so an
10371
10954
  x86_64 Linux or Windows machine running stim-server can build for an Apple silicon Mac. Its JDK major
10372
- version must match, and its Android SDK must hold the NDK, build-tools and
10955
+ version must match this machine's, which can be an installed JDK that is not the
10956
+ worker's default: its offer lists \`jdks\`, and a build sent with the build-jdk
10957
+ feature runs with JAVA_HOME at the one of that major. A major no installed JDK
10958
+ has gets the install command for the worker's OS as doctor's fix. A nearby major
10959
+ never counts (Gradle, AGP and Kotlin support and the shared build cache assume
10960
+ one JDK). Also, its Android SDK must hold the NDK, build-tools and
10373
10961
  compile platform that the project's React Native version names in
10374
- gradle/libs.versions.toml; Gradle and AGP come from the synced project. When
10962
+ gradle/libs.versions.toml; Gradle and AGP come from the synced project. A
10963
+ package the SDK lacks is a note, not a blocker, when its offer reports the SDK
10964
+ licenses accepted (\`licenses/android-sdk-license\`) and the app's
10965
+ Gradle root's gradle.properties does not set android.builder.sdkDownload=false:
10966
+ Gradle downloads the package on the first build there (the NDK is about 1-2 GB).
10967
+ Otherwise it blocks, and doctor's fix is the sdkmanager commands to run on that
10968
+ machine; a stim-server that does not report its licenses keeps that rule. When
10375
10969
  no machine takes the build in auto placement, the run prints one line with each machine's
10376
10970
  reason, such as \`janics-mac-mini: busy (load at or above 2/core; load
10377
10971
  8.2/core, 2 builds) -> building here\`, and compiles here. It sends the files
@@ -10510,6 +11104,9 @@ be setup steps are supplied by Stim on the command lines it composes itself:
10510
11104
  SWIFT_OTHER_PREFIX_MAPPINGS on a Swift 6.4+ toolchain -- so no
10511
11105
  Podfile post_install block (Xcode 26+ only, and skipped when the
10512
11106
  project configured ccache, which defeats it)
11107
+ Debug builds for the iOS simulator also get
11108
+ DEBUG_INFORMATION_FORMAT=dwarf, which skips the dSYM bundle;
11109
+ Release and device builds keep the project's setting
10513
11110
  gradlew --build-cache -- so no org.gradle.caching=true in a committed
10514
11111
  gradle.properties. Debug builds add
10515
11112
  -PreactNativeArchitectures=<target ABI> when the owned
@@ -10968,7 +11565,7 @@ var macos_default = {
10968
11565
 
10969
11566
  If Stim is not installed globally, replace stim with npx stim.
10970
11567
 
10971
- ios --remote <machine> shares remote.machines and its separate device-host
11568
+ ios --remote <placement> shares remote.machines and its separate device-host
10972
11569
  approval. Read guide lifecycle hosted-ios for simulator placement and Metro.
10973
11570
 
10974
11571
  Run from the directory containing Package.swift. This prototype builds one
@@ -11072,7 +11669,7 @@ Apple Swift, xcrun and xcodebuild selections can be reproduced; custom launchers
11072
11669
  require the checkout and otherwise miss safely. SWIFT_EXEC and SWIFT_EXEC_MANIFEST
11073
11670
  compiler overrides are resolved and versioned separately and require the checkout.
11074
11671
  The SDK selected by SDKROOT or SWIFTPM_SDKROOT_macosx is checked by real path and build version. Explicit local placement,
11075
- remote build mode off, disabled pool members and remoteBuildCache
11672
+ disabled pool members and remoteBuildCache
11076
11673
  false skip peer lookup. Older workers keep their normal compilation path.
11077
11674
  --plan validates the Swift Package directory, macos settings, development plist
11078
11675
  and declared resources without building, signing, stopping or launching
@@ -11104,17 +11701,16 @@ line instead of when the app exits.
11104
11701
 
11105
11702
  BUILD OFFLOAD
11106
11703
 
11107
- --remote-build <auto|local|name> overrides STIM_REMOTE_BUILD and the
11704
+ --remote-build <local|auto|remote|remote-only|name> overrides STIM_REMOTE_BUILD and the
11108
11705
  machine setting remote.build (default auto). local builds here; a name
11109
- requires the matching configured and paired remote Mac, ignoring remote.buildMode
11110
- and this Mac's capacity. Any failure is STIM_OFFLOAD_REFUSED without a local xcodebuild, Gradle or
11706
+ requires the matching configured and paired remote Mac, ignoring this Mac's capacity. Any failure is STIM_OFFLOAD_REFUSED without a local xcodebuild, Gradle or
11111
11707
  SwiftPM compile or another machine. Invalid, unlisted or unpaired selections
11112
11708
  refuse before stopping the app or changing its build record. See stim guide errors STIM_OFFLOAD_REFUSED.
11113
11709
 
11114
- With remote.build auto, remote.buildMode places these SwiftPM Debug builds:
11115
- auto builds here while this Mac has capacity, force uses an approved build
11116
- machine when one accepts, and off builds here when local remains in the automatic
11117
- build pool. remote.buildPoolDisabled excludes members from automatic placement,
11710
+ remote.build places these SwiftPM Debug builds: auto builds here while this Mac has
11711
+ capacity, remote uses the first approved build machine in remote.machines order that
11712
+ accepts and otherwise builds here, remote-only never builds here and refuses when none
11713
+ accepts, and local builds here. remote.buildPoolDisabled excludes members from automatic placement,
11118
11714
  including its local fallback. Explicit local or named placement bypasses membership.
11119
11715
  Configure remote.machines
11120
11716
  and approve build access as described in stim guide settings. The worker needs
@@ -11136,8 +11732,8 @@ identity comes from the worker's toolchain. Any other combination keeps the matc
11136
11732
  The client validates the development plist and resource entries before asking a machine. It verifies
11137
11733
  the returned archive's sha256, bundle identifier, executable, declared resources
11138
11734
  and ad hoc signature
11139
- before replacing the owned bundle, then launches locally as usual. With auto, every offload
11140
- failure falls back to the local build, including force. Failed staging preserves
11735
+ before replacing the owned bundle, then launches locally as usual. With auto or remote, every offload
11736
+ failure falls back to the local build. Failed staging preserves
11141
11737
  the previous bundle. No failed artifact is promoted or cached. The build record
11142
11738
  is unchanged by invalid, unlisted or unpaired setup refusals. It
11143
11739
  carries buildMachine (selected) and builtOn (here or the machine, absent until
@@ -11299,19 +11895,40 @@ used.
11299
11895
 
11300
11896
  ON ANOTHER MAC
11301
11897
 
11302
- stim macos --remote <machine> builds the Debug app on this Mac and runs it on an
11898
+ stim macos --remote <machine> or macos.remote builds the Debug app on this Mac and runs it on an
11303
11899
  approved remote Mac over the tailnet, without SSH. The Mac must be listed
11304
11900
  in remote.machines and approved: stim doctor --fix makes a setup ticket, a
11305
11901
  person on that Mac runs stim-server setup with that ticket and expiry (guide
11306
11902
  settings), and stim doctor then pins that Mac's stim-server key. Stim goes ahead
11307
11903
  only with the stim-server that proves it holds that key.
11308
- A refusal or an unreachable host fails the command; it never launches here
11904
+ A refusal or an unreachable host fails the command; with a named Mac it never launches here
11309
11905
  instead.
11310
11906
 
11311
- The --remote value is a remote Mac name from remote.machines. macOS refuses
11312
- eas and proxy because it has neither backend. It also refuses auto until
11313
- automatic placement ships. These reserved names are case-insensitive and
11314
- trimmed; they refuse with STIM_BAD_ARG before state access or a connection.
11907
+ The --remote flag and the macos.remote setting take the placement words of
11908
+ ios.remote and a remote Mac name from remote.machines; the flag overrides the
11909
+ setting:
11910
+
11911
+ local launch here (the default when neither is set)
11912
+ auto launch here unless this Mac is busy (host memory pressure or load
11913
+ per core at or above server.maxLoadPerCore), then on the first
11914
+ approved Mac in remote.machines order that has room
11915
+ remote on the first approved Mac in remote.machines order that has room
11916
+ and a load per core under its own server.maxLoadPerCore, here only
11917
+ when none does
11918
+ remote-only the same, never here, so a macOS app never takes focus on this Mac:
11919
+ when every Mac that answered is only full or loaded it waits up to
11920
+ 600 seconds for one with room and then refuses with STIM_AT_CAPACITY;
11921
+ with no Mac that could ever take it (none configured, unreachable,
11922
+ declined for another reason) it refuses at once with
11923
+ STIM_HOSTING_REFUSED and the reason each Mac was skipped
11924
+ <machine> exactly that Mac, with no fallback
11925
+
11926
+ A placement word picks the Mac on a new launch and keeps the recorded one while an
11927
+ app runs there, until stim stop. The command prints a placement: line with the
11928
+ choice and the skipped Macs. macOS refuses eas and proxy because it has neither
11929
+ backend. Reserved names are case-insensitive and trimmed; they refuse with
11930
+ STIM_BAD_ARG before state access or a connection. The device pool switches
11931
+ (remote.devicePoolDisabled) also apply to automatic macOS placement.
11315
11932
 
11316
11933
  Logs: the host records the hosted app's stdout, stderr and exit like a local run.
11317
11934
  stim logs, including --errors, --json and --follow, asks the host over the same