stim 1.26.0 → 1.28.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 (172) hide show
  1. package/dist/{acquire-artifact-DVsvSwf7.mjs → acquire-artifact-CKByBnTD.mjs} +2 -2
  2. package/dist/{act-C-BqJEpu.mjs → act-8iswyfRa.mjs} +319 -126
  3. package/dist/{activity-Lnb_EoRY.mjs → activity-DRMg4_mD.mjs} +6 -6
  4. package/dist/{agent-device-usage-output-DbXy5tg8.mjs → agent-device-usage-output-tE90fDdo.mjs} +5 -5
  5. package/dist/{android-CiNQw3kq.mjs → android-CHmSU4B4.mjs} +101 -45
  6. package/dist/{android-DTAcRwBC.mjs → android-CsPs-7Mg.mjs} +23 -18
  7. package/dist/{android-B5P81yI-.mjs → android-D577ApKG.mjs} +37 -15
  8. package/dist/{android-CSbLRD53.mjs → android-YWCbmkVK.mjs} +5 -5
  9. package/dist/{android-cas-BTVhZ0Qs.mjs → android-cas-_aDifWW0.mjs} +4 -4
  10. package/dist/android-cas-compiler.mjs +1 -1
  11. package/dist/api.d.mts +14 -4
  12. package/dist/api.mjs +39 -29
  13. package/dist/{apk-signed-tail-B46U9hXc.mjs → apk-signed-tail-CQbwM0qO.mjs} +2 -2
  14. package/dist/{artifact-CsAP6AGK.mjs → artifact-DYtHjxsn.mjs} +19 -24
  15. package/dist/{artifact-BxREhAaV.mjs → artifact-bFkfD3vf.mjs} +18 -23
  16. package/dist/{artifact-platform-CMRc0M_r.mjs → artifact-platform-BO2Uvk_A.mjs} +1 -1
  17. package/dist/auto-placement-BVqs8AJi.mjs +350 -0
  18. package/dist/{browser-web-rYPyqhTf.mjs → browser-web-BUaLcafY.mjs} +16 -16
  19. package/dist/{build-DPhApsjV.mjs → build-cQURy_KW.mjs} +35 -31
  20. package/dist/{build-cache-DrXNCy3T.mjs → build-cache-CZULpXVl.mjs} +43 -21
  21. package/dist/{build-plan-DrMMdayj.mjs → build-plan-CxoYfxsc.mjs} +7 -6
  22. package/dist/{cache-manifest-BEZ9Bpl1.mjs → cache-manifest-DEjqGGat.mjs} +1 -1
  23. package/dist/cache-manifest.mjs +1 -1
  24. package/dist/{budget-k2mZGvvk.mjs → ccache-Bm_A1xX9.mjs} +562 -1604
  25. package/dist/{cdp-BG3FJCCx.mjs → cdp-C3svvXiE.mjs} +1 -1
  26. package/dist/{chrome-BvV8ssI3.mjs → chrome-DyDWurWG.mjs} +1 -1
  27. package/dist/{cli-BBKFGOFl.mjs → cli-CpbaLDa7.mjs} +24 -23
  28. package/dist/cli.mjs +1 -1
  29. package/dist/{client-D9CaRks-.mjs → client-YxCer6ab.mjs} +84 -65
  30. package/dist/collector-run.d.mts +10 -9
  31. package/dist/collector-run.mjs +42 -27
  32. package/dist/{command-output-BF5AR2dL.mjs → command-output-C8R1KIvi.mjs} +1 -1
  33. package/dist/{config-BktkY_zC.mjs → config-Cn94Jc0g.mjs} +23 -4
  34. package/dist/{dependencies-Dw6wfQdv.mjs → dependencies-DKeMzwXY.mjs} +108 -60
  35. package/dist/{dependency-state-DFB41vfr.mjs → dependency-state-CojA2o0t.mjs} +14 -7
  36. package/dist/{detached-entry-KmieOKgN.mjs → detached-entry-BPlSaCwC.mjs} +1 -1
  37. package/dist/{dev-client-DXR_tPVY.mjs → dev-client-D-BW5uiq.mjs} +2 -2
  38. package/dist/{device-BK-zwyd_.mjs → device-CgXfbcz5.mjs} +25 -24
  39. package/dist/{device-capacity-DCSvUF0l.mjs → device-capacity-Cd8lMSn3.mjs} +175 -22
  40. package/dist/{device-BcYeHSFu.mjs → device-eeK95Lir.mjs} +16 -16
  41. package/dist/device-host-worker.mjs +22 -20
  42. package/dist/{device-ios-BLGfXQeK.mjs → device-ios-Cc9ulihb.mjs} +251 -23
  43. package/dist/{device-lease-run-BmvIl7bI.mjs → device-lease-run-Mtzp4THX.mjs} +5 -5
  44. package/dist/{device-pool-Dluix4H3.mjs → device-pool-eRz6DtCg.mjs} +3 -3
  45. package/dist/{doctor-Bhg1ii70.mjs → doctor-Bz_ouYAW.mjs} +28 -16
  46. package/dist/{doctor-Cp-BhzTS.mjs → doctor-C2QyhejJ.mjs} +1 -1
  47. package/dist/{doctor-fingerprint-D3a04Mhu.mjs → doctor-fingerprint-o7ZI9YLn.mjs} +1 -1
  48. package/dist/doctor-fingerprint.mjs +1 -1
  49. package/dist/{doctor-watchman-BStK05nt.mjs → doctor-watchman-BTW82PDC.mjs} +5 -5
  50. package/dist/{doctor-OnwP94ne.mjs → doctor-xf7ht-5n.mjs} +76 -32
  51. package/dist/{eas-build-Dsj93iM9.mjs → eas-build-Dm6q68QY.mjs} +4 -4
  52. package/dist/eas-fallback-DajNpxf-.mjs +87 -0
  53. package/dist/{eas-session-ledger-B5vygTiD.mjs → eas-session-ledger-CBFo_Uo1.mjs} +37 -8
  54. package/dist/{error-diagnostics-BOOm9Db7.mjs → error-diagnostics-D1QfyJzQ.mjs} +51 -25
  55. package/dist/{exec-VFRpu1iA.mjs → exec-aULwtNly.mjs} +57 -6
  56. package/dist/{gc-ldgr-hXU.mjs → gc-DM5oMm-J.mjs} +161 -45
  57. package/dist/{gc-C_79zvz4.mjs → gc-PLFwb0k4.mjs} +2 -2
  58. package/dist/{guide-D7K0X6KG.mjs → guide-C4I46XAk.mjs} +674 -213
  59. package/dist/{hosted-android-Bk54ej6n.mjs → hosted-android-DlaKLbW0.mjs} +6 -7
  60. package/dist/{hosted-client-fHO8yqzc.mjs → hosted-client-DDRm9hPJ.mjs} +51 -20
  61. package/dist/{hosted-logs-BfbmDM4M.mjs → hosted-logs-bSebOKbz.mjs} +8 -8
  62. package/dist/{hosted-logs-sync-B0pvmOk-.mjs → hosted-logs-sync-B0c53W_c.mjs} +5 -5
  63. package/dist/{hosted-macos-CPl0OZ9h.mjs → hosted-macos-B7-8Pv4H.mjs} +6 -6
  64. package/dist/{hosted-native-CpDB3-PO.mjs → hosted-native-CDe33CSr.mjs} +22 -18
  65. package/dist/{idle-shutdown-FfxL1KdG.mjs → idle-shutdown-CQ0QwU9J.mjs} +7 -8
  66. package/dist/{ios-WxpDJ5T1.mjs → ios-B1DYuOjh.mjs} +27 -11
  67. package/dist/{ios-B7-lToDu.mjs → ios-B1HvafEG.mjs} +8 -8
  68. package/dist/{ios-ENC4sRPA.mjs → ios-CsRVik64.mjs} +7 -1
  69. package/dist/{ios-device-CsyryvpT.mjs → ios-device-B4elVFKi.mjs} +3 -3
  70. package/dist/{ios-device-B6HK951C.mjs → ios-device-CiBA-er9.mjs} +1 -1
  71. package/dist/{ios-project-Ty5pHl9O.mjs → ios-project-CR4mZ7CH.mjs} +22 -9
  72. package/dist/{ios-signing-4c_qXvQm.mjs → ios-signing-BJzvDPfU.mjs} +2 -2
  73. package/dist/{ios-state-Cw7-WQf2.mjs → ios-state-BPHqMDdZ.mjs} +1 -1
  74. package/dist/{ios-ftxdQbJS.mjs → ios-tg4tp3q1.mjs} +94 -37
  75. package/dist/{js-swap-BMhXYVDS.mjs → js-swap-CVpSW1fn.mjs} +8 -7
  76. package/dist/{launch-DOp8RjDz.mjs → launch-DoySmhjz.mjs} +42 -38
  77. package/dist/lightened-DFAJgfIk.mjs +171 -0
  78. package/dist/{logs-s1dRIG1E.mjs → logs-BGsW7iPe.mjs} +46 -21
  79. package/dist/{logs-dQDki6Tt.mjs → logs-DI5ILkYB.mjs} +7 -7
  80. package/dist/{macos-DBBPNnIA.mjs → macos-Bo9st9Di.mjs} +73 -38
  81. package/dist/{macos-B0VEV3Gf.mjs → macos-C12RMJQF.mjs} +1 -1
  82. package/dist/macos-run.mjs +1 -1
  83. package/dist/maintenance-run.mjs +7 -7
  84. package/dist/{metro-CjC10t6E.mjs → metro-BnhXV2Mj.mjs} +4 -4
  85. package/dist/{metro-gateway-B6_PgD0L.mjs → metro-request-DXyKjVDF.mjs} +26 -47
  86. package/dist/{named-ports-BG0j1X4g.mjs → named-ports-Phoi2POD.mjs} +3 -3
  87. package/dist/{native-android-CL2NGiik.mjs → native-android-vxgtLD7w.mjs} +17 -18
  88. package/dist/{native-gradle-artifact-inputs-btSUulme.mjs → native-gradle-artifact-inputs-BN96IVw8.mjs} +5 -5
  89. package/dist/{native-runtime-Dq3RBcON.mjs → native-runtime-DG4Rodzj.mjs} +9 -8
  90. package/dist/{native-source-D-4Dh4JU.mjs → native-source-D1AzWiuP.mjs} +1 -1
  91. package/dist/{native-xcode-inputs-BW7H8JGu.mjs → native-xcode-inputs-BKarCoKg.mjs} +3 -3
  92. package/dist/{native-xcode-ios-CBXJAxfC.mjs → native-xcode-ios-8A-efGa5.mjs} +17 -17
  93. package/dist/{ndjson-CgE_o3Dl.mjs → ndjson-Dy2W4vRa.mjs} +2 -2
  94. package/dist/offload-worker.mjs +23 -24
  95. package/dist/{operation-exec-CUZifp6Z.mjs → operation-exec-Dp4FuIGR.mjs} +1 -1
  96. package/dist/{ownership-BjYvMNX_.mjs → ownership-DJCq48IJ.mjs} +3 -3
  97. package/dist/{page-CzEA_-5_.mjs → page-CQJlcj3x.mjs} +2 -2
  98. package/dist/{phone-reverses-lmj6kcPm.mjs → phone-reverses-BzTa8g1F.mjs} +18 -4
  99. package/dist/{placement-log-DIFPQAXp.mjs → placement-log-Cvhta5ib.mjs} +3 -3
  100. package/dist/{plan-placement-IiiSQhtz.mjs → plan-placement-WkZdccuN.mjs} +36 -10
  101. package/dist/{ports-C2_w1Z0Z.mjs → ports-UV4mJgyC.mjs} +3 -3
  102. package/dist/{prebuild-gmouvmJZ.mjs → prebuild-k8d_tqOq.mjs} +16 -17
  103. package/dist/{project-BUu6Is4k.mjs → project-Sui52ywT.mjs} +17 -17
  104. package/dist/{pull-request-CbNlkFok.mjs → pull-request-BzLmzAIQ.mjs} +2 -2
  105. package/dist/pull-requests.mjs +1 -1
  106. package/dist/{react-native-android-Cgg99REN.mjs → react-native-android-DANlGGQf.mjs} +32 -26
  107. package/dist/{react-native-build-oNwqF5Wb.mjs → react-native-build-jUHnIfZ9.mjs} +9 -9
  108. package/dist/{react-native-doctor-BhA3KUte.mjs → react-native-doctor-DCWRjS7K.mjs} +6 -6
  109. package/dist/{react-native-ios-B-nf4rjN.mjs → react-native-ios-BwMsFMXi.mjs} +23 -17
  110. package/dist/{recordings-BldzVKq0.mjs → recordings-iy0mkm7b.mjs} +1 -1
  111. package/dist/{registration-CN6nlhcD.mjs → registration-BwCe64PH.mjs} +1 -1
  112. package/dist/{reload-C9OduiNc.mjs → reload-Bj7QyuDy.mjs} +1 -1
  113. package/dist/{reload-CCR7KC_l.mjs → reload-c_uEyc_X.mjs} +16 -16
  114. package/dist/{remote-C8S7FlPF.mjs → remote-DFlpAqdV.mjs} +23 -10
  115. package/dist/{result-7F5hEwQJ.mjs → result-BIcXREyC.mjs} +32 -21
  116. package/dist/{run-BqB0v3pt.mjs → run-y2HwyNvg.mjs} +3 -3
  117. package/dist/{selection-BNPBZ6T5.mjs → selection-CXgM_VK_.mjs} +37 -29
  118. package/dist/{server-bare-A75rDIdD.mjs → server-bare-2N4vSRZG.mjs} +2 -2
  119. package/dist/{server-command-CssKFOD7.mjs → server-command-qneHPcRI.mjs} +6 -6
  120. package/dist/{server-expo-eQfOdydb.mjs → server-expo-Ccm9zB0o.mjs} +4 -4
  121. package/dist/server-expo-WulkyAwT.mjs +2 -0
  122. package/dist/{settings-CosW5X-S.mjs → settings-CY5JZlNN.mjs} +375 -62
  123. package/dist/{settings-OaGQF-ri.mjs → settings-Cfd_Q6ys.mjs} +24 -3
  124. package/dist/settings-payload-DwQiEMCn.mjs +257 -0
  125. package/dist/settings-write-CJpjkeAd.mjs +185 -0
  126. package/dist/settings.schema.json +51 -33
  127. package/dist/{simslim-Bax1qcxu.mjs → simslim-DCQwzoPy.mjs} +2 -2
  128. package/dist/{slot-launch-CYCEhfBY.mjs → slot-launch-BpmanOnk.mjs} +6 -6
  129. package/dist/{stage-jKQ7CP_B.mjs → stage-CmugrSG5.mjs} +2 -2
  130. package/dist/{start-C_wefNKB.mjs → start-CHH3zgIU.mjs} +1 -1
  131. package/dist/{start-_BdJ32cZ.mjs → start-DYLk6GXn.mjs} +27 -22
  132. package/dist/{state-C1i2nMOP.mjs → state-B2QJbDwz.mjs} +4 -4
  133. package/dist/{state-C4OdpOTF.mjs → state-BXC1Lfxw.mjs} +2 -2
  134. package/dist/{stats-tEpEEH-u.mjs → stats-DxKqhcGp.mjs} +5 -5
  135. package/dist/{stats-report-n2U5plhk.mjs → stats-report-B86Cv7Uc.mjs} +1 -1
  136. package/dist/{status-CRXz-Gtj.mjs → status-7ULDEJ1e.mjs} +1 -1
  137. package/dist/{status-DQrqodLj.mjs → status-BHCkOZga.mjs} +51 -28
  138. package/dist/{status-B9M7x4CZ.mjs → status-D_Ljc89p.mjs} +1 -1
  139. package/dist/{status-watch-B5wDIH_o.mjs → status-watch-BmsPqYSU.mjs} +18 -7
  140. package/dist/status-worker.mjs +1 -1
  141. package/dist/{stim-desktop-6Dqp-gjX.mjs → stim-desktop-CbWo21kb.mjs} +2 -2
  142. package/dist/{stop-DRqQVqDJ.mjs → stop-D-dGE9gh.mjs} +21 -20
  143. package/dist/{stop-ySNQHXMG.mjs → stop-DQvE-P76.mjs} +10 -6
  144. package/dist/{stop-CmdNhGlC.mjs → stop-b2pouxAO.mjs} +2 -2
  145. package/dist/supervisor-run.mjs +237 -63
  146. package/dist/{support-BGSCBoib.mjs → support-04IhBUTT.mjs} +6 -6
  147. package/dist/{support-CVOCp0Ft.mjs → support-BtlJKjyQ.mjs} +12 -11
  148. package/dist/{swiftpm-artifact-inputs-CfUzt_0Z.mjs → swiftpm-artifact-inputs-Cj2UpmPN.mjs} +4 -4
  149. package/dist/{swiftpm-macos-x6HPpl_2.mjs → swiftpm-macos-BhF2stxY.mjs} +11 -12
  150. package/dist/{toolchain-J4J8MrAr.mjs → toolchain-mLA_km-b.mjs} +321 -15
  151. package/dist/{toolchain-DoavrvIA.mjs → toolchain-qktiV8xc.mjs} +1 -1
  152. package/dist/{trigger-KVaWWNyn.mjs → trigger-DMou-X7j.mjs} +2 -2
  153. package/dist/trigger-qp2ji5wE.mjs +2 -0
  154. package/dist/{warm-progress-DvhTX_eu.mjs → warm-progress-B0Vpuvpl.mjs} +731 -727
  155. package/dist/{web-Bihbf7-u.mjs → web-BYmK0TjL.mjs} +9 -9
  156. package/dist/web-CrVLVQZY.mjs +2 -0
  157. package/dist/web-run.mjs +9 -9
  158. package/dist/windows-report.mjs +1 -1
  159. package/dist/{workspace-state-pUg3_RRA.mjs → workspace-state-kIZrdlSg.mjs} +3 -3
  160. package/dist/{workspaces-g2lhdBXF.mjs → workspaces-CmOcceDN.mjs} +1879 -74
  161. package/dist/{worktree-BKuklFh_.mjs → worktree-2dk5edza.mjs} +933 -534
  162. package/dist/worktree-nliAoHxB.mjs +2 -0
  163. package/package.json +7 -7
  164. package/dist/ccache-Bx5TbxwG.mjs +0 -143
  165. package/dist/eas-fallback-FwkeqqGi.mjs +0 -381
  166. package/dist/hosted-ios-D55QhYaD.mjs +0 -15
  167. package/dist/native-run-BfIDFKLH.mjs +0 -275
  168. package/dist/server-expo-wvcj5tiZ.mjs +0 -2
  169. package/dist/settings-write-QjYfSrjF.mjs +0 -360
  170. package/dist/trigger-DzJu208Q.mjs +0 -2
  171. package/dist/web-BFLueSh-.mjs +0 -2
  172. package/dist/worktree-Bm4cb-nU.mjs +0 -2
@@ -1,8 +1,8 @@
1
- import { t as ANDROID_AVD_CONFIG_HELP } from "./settings-CosW5X-S.mjs";
2
- import { i as findProjectRoot$1 } from "./project-BUu6Is4k.mjs";
3
- import { h as RECENT_LAUNCH_MS } from "./eas-session-ledger-B5vygTiD.mjs";
4
- import { o as guideStatus, t as WATCHMAN_NESTED_WORKTREES } from "./doctor-watchman-BStK05nt.mjs";
5
- import "./status-watch-B5wDIH_o.mjs";
1
+ import { t as ANDROID_AVD_CONFIG_HELP } from "./settings-CY5JZlNN.mjs";
2
+ import { i as findProjectRoot$1 } from "./project-Sui52ywT.mjs";
3
+ import { h as RECENT_LAUNCH_MS } from "./eas-session-ledger-CBFo_Uo1.mjs";
4
+ import { o as guideStatus, t as WATCHMAN_NESTED_WORKTREES } from "./doctor-watchman-BTW82PDC.mjs";
5
+ import "./status-watch-BmsPqYSU.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
@@ -297,16 +297,20 @@ reserved named ports, even outside the workspace. Read guide ports before use.
297
297
 
298
298
  Ask the user before these actions:
299
299
 
300
- - worktree remove, because it deletes the worktree and gives up its owned
301
- 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
302
302
  a Stim registry entry. Git-created branches are kept; a branch with an
303
303
  existing Stim ownership record is deleted only when it has no unique commits.
304
304
  - worktree remove --force, because it also discards uncommitted and untracked
305
305
  files.
306
306
  - gc --delete, because it deletes orphaned resources, removes clean linked
307
307
  worktrees whose branch or pull request is merged (or whose pull request
308
- was closed), and clears the build outputs of every
309
- 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
310
314
  the user the entries its sections list (guide facts gc). gc --delete
311
315
  --cache all empties the shared build caches and those outputs instead, and
312
316
  gc --delete --cache workspaces clears only the outputs; both inspect
@@ -327,8 +331,10 @@ Ask the user before these actions:
327
331
 
328
332
  LEAVE THE MACHINE CLEAN
329
333
 
330
- Run stim stop when you pause or finish a task, and stim worktree remove once
331
- 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;
332
338
  every device comes from Stim. Before starting a new workspace, check stim
333
339
  status for an idle workspace to reuse and confirm capacity headroom instead of
334
340
  adding another. Never quit Device Hub or Simulator.app: on Xcode 27
@@ -1335,7 +1341,8 @@ ON FAILURE
1335
1341
  An \`ios\` or \`android\` run that \`stim stop\` or Ctrl-C interrupted
1336
1342
  reports STIM_CANCELLED and exits 130. A
1337
1343
  run that exits on the interrupt at once (a second Ctrl-C, or Ctrl-C with no
1338
- 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.
1339
1346
 
1340
1347
  \`stop --json\` prints { root, ok, supervisor, collectors, metro, device,
1341
1348
  port, metroTunnel, releasedLeases, macos? }. macos is { status: stopped | failed,
@@ -1532,7 +1539,8 @@ RULES
1532
1539
  why) or "failed" (detail says why and what to retry).
1533
1540
  kind: device, parkedDevice, parkedHostedDevice, idleDevice,
1534
1541
  deviceRecord,
1535
- workspaceOutputs, recording, workspaceDirectory, project,
1542
+ workspaceOutputs, worktreeDependencies, recording,
1543
+ workspaceDirectory, project,
1536
1544
  buildLock, buildSlot, deviceLease, easSession, worktree,
1537
1545
  cache, gradleVersion, tempLeftover (one "done" labelled
1538
1546
  with the TMPDIR it cleared, plus a "kept" or "failed" per
@@ -1600,9 +1608,9 @@ RULES
1600
1608
  else null. buildState is { bytes, dirs:
1601
1609
  { path, bytes } } for the in-tree build and
1602
1610
  dependency directories automatic maintenance
1603
- clears (ios/build, android/app/build,
1604
- android/build, android/.gradle, .build,
1605
- 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
1606
1614
  project root, or apps/* and packages/* for
1607
1615
  build directories) and a root .pnpm-store,
1608
1616
  paths relative to the worktree, largest
@@ -1701,18 +1709,29 @@ RULES
1701
1709
  Metro, client and device logs over twice the
1702
1710
  8 MiB cap; willTrim marks the ones it would
1703
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
1704
1717
  recordings { dir, projectRoot, bytes, deleteBytes,
1705
1718
  willDelete, withWorkspace, reason, detail }
1706
1719
  recordings/ of each workspace; deleteBytes is
1707
1720
  what --delete would remove; withWorkspace
1708
1721
  marks those of a gone workspace, removed with
1709
1722
  its workspace directory
1710
- workspaceBuildOutputs { dir, projectRoot, bytes, idleDays, willClear,
1711
- reason, detail } derived-data, gradle-build,
1712
- android-cas, cache-provider, macos/build,
1713
- macos/<Product>.app and macos/staging-* of each
1714
- workspace; willClear marks the ones --delete
1715
- 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
1716
1735
  directories that --delete removes whole (dead
1717
1736
  projects and orphanedWorkspaces), and --cache
1718
1737
  all does not
@@ -1771,11 +1790,13 @@ RULES
1771
1790
  bytes, willDelete, kept }] } | null ignored
1772
1791
  directories worktree remove moved out of a
1773
1792
  worktree into $STIM_HOME/trash before git
1774
- 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
1775
1795
  deleting the entry. kept names the live or
1776
1796
  unresolved ownership claim, the missing or
1777
1797
  invalid trash.json, or the worktree that
1778
- still exists, that keeps a directory;
1798
+ still exists (not for a warm entry), that
1799
+ keeps a directory;
1779
1800
  --delete deletes the rest, only remove-*
1780
1801
  entries with a valid trash.json. null when the
1781
1802
  trash is empty or under any --cache scope
@@ -1813,8 +1834,8 @@ RULES
1813
1834
  reason is null for an entry --delete acts on, and otherwise a stable code
1814
1835
  to branch on; detail is the text line, for the user. Never parse detail.
1815
1836
  A linked worktree's detail is never null.
1816
- workspaceBuildOutputs unresolved | in-use | last-use-unknown |
1817
- recently-used
1837
+ workspaceBuildOutputs unresolved | locked | lock-unknown | in-use |
1838
+ last-use-unknown | recently-used
1818
1839
  workspaceLogs unresolved | in-use | collector
1819
1840
  recordings unresolved | retained | recently-recorded
1820
1841
  memory in-use | busy | unknown | build-running |
@@ -2273,6 +2294,10 @@ RULES
2273
2294
  build: ios compile on janics-mac-mini (build, 2m10s), 3m05s elapsed
2274
2295
  build: ios launch (bundling JS 45%, 12s), 58s elapsed
2275
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
+
2276
2301
  An environment with a recorded run also carries lastBuilds, each
2277
2302
  platform's most recent ios or android run, finished or failed:
2278
2303
 
@@ -2455,7 +2480,35 @@ RULES
2455
2480
 
2456
2481
  capacity.committedMb sums memoryMb; in a \`status --watch --json\` line each
2457
2482
  figure, including an environment's memoryMb, is held on its own (see
2458
- \`guide lifecycle\`), so the sums can differ from what they sum. The memory budget plans before a boot
2483
+ \`guide lifecycle\`), so the sums can differ from what they sum.
2484
+
2485
+ capacity.load is always present. capacity.builds is present only while
2486
+ concurrency.maxBuilds is set, and capacity.devices only while
2487
+ concurrency.maxDevices is set (see \`guide lifecycle concurrency\`):
2488
+
2489
+ load { cpus, loadPerCore, busyAt, limit, state }. loadPerCore is the
2490
+ 5-minute load average divided by cpus, to one decimal. busyAt is
2491
+ server.maxLoadPerCore (\`guide settings\`). state is free under
2492
+ busyAt, busy at or over it, where automatic placement prefers
2493
+ another machine, or holding at or over limit. limit is null
2494
+ because no setting sets it yet, so state is never holding.
2495
+
2496
+ builds { max, inUse, holders: [{ slot, workspace, client, since,
2497
+ unresolved? }] }. inUse counts the slots below max that a new
2498
+ build cannot take. workspace is the root of the local build in
2499
+ the slot; client is the device id of the client whose offloaded
2500
+ build this machine runs in it. An unresolved slot has a claim
2501
+ whose holder Stim cannot identify; both are null, and the slot
2502
+ stays in use until that claim is removed.
2503
+ devices { max, inUse, unknown?, holders: [{ platform, label, workspace,
2504
+ slot, booting? }] }, the owned devices the cap counts: booted or
2505
+ booting stim simulators, owned emulators adb lists, and boots in
2506
+ progress. label is the label of the workspace that records the
2507
+ device, with its device slot; for a device no workspace records,
2508
+ label is its name and workspace and slot are null. inUse is null
2509
+ and unknown says why when the devices could not be listed.
2510
+
2511
+ The memory budget plans before a boot
2459
2512
  and always uses the estimate. What is using CPU and memory now is the
2460
2513
  top-level machine section:
2461
2514
 
@@ -2550,9 +2603,10 @@ RULES
2550
2603
  plan does not, so changes compare the fingerprint before that
2551
2604
  prebuild; changeCount 0 then means those inputs match the
2552
2605
  baseline. rekeyedBy is empty.
2553
- placement with ios.remote or android.remote set to auto or a Mac,
2554
- where the plan assumes the device runs: "on <machine>" or
2555
- "this machine; auto may use <machines>"; absent otherwise
2606
+ placement with ios.remote or android.remote set to auto, remote,
2607
+ remote-only or a Mac, where the plan assumes the device runs:
2608
+ "on <machine>" or "this machine; <word> may use <machines>";
2609
+ absent otherwise
2556
2610
  refusal { code, message, remedy } when the run would refuse:
2557
2611
  STIM_PREBUILD_FAILED for a tracked native dir the fingerprint
2558
2612
  leaves out, STIM_EAS_BUILD_MISSING for an EAS miss
@@ -2707,9 +2761,9 @@ BUILD PLACEMENT (\`offload\`)
2707
2761
  decision is "here", "offloaded", or "fell-back" (it tried a remote machine
2708
2762
  and built here). reason is why: the run's \`placement:\` reason, such as
2709
2763
  "load 0.6/core, 1 of 3 build slots busy here" (auto keeps the build here
2710
- while this machine has room) or "remote.buildMode is off" (which prints no line),
2764
+ while this machine has room) or "--remote-build local" (which prints no line),
2711
2765
  or for a fallback the cause, prefixed by the machine it concerns, such as
2712
- "mini: no less loaded (load 1.2/core there, 0.6/core here)". buildMs is the compile here, or the offloaded build's
2766
+ "mini: saturated: load 2.4/core there, at or above 2/core". buildMs is the compile here, or the offloaded build's
2713
2767
  total (offer, sync, build and fetch); localEstimateMs is this project's
2714
2768
  last cold build here before the run. A run that hits the cache or waits
2715
2769
  for another workspace's build compiles nothing and records none.
@@ -2753,19 +2807,19 @@ comes from \`start --remote\`, eas/proxy in \`ios.remote\`, or \`android.remote\
2753
2807
 
2754
2808
  HOSTED IOS METRO
2755
2809
  ios --remote <machine> keeps Metro local and needs no start --remote.
2756
- The supervisor watches private gateway requests in workspace state, binds
2757
- only this machine's Tailscale address and accepts only the pinned hosting peer
2758
- with a per-session secret. The host's bridge is loopback-only. For an Expo
2759
- dev client it listens on this machine's Metro port, which the dev server
2810
+ The supervisor watches hosted Metro requests in workspace state and keeps a
2811
+ few authenticated connections open to the host for each session. The host
2812
+ pairs one with each connection the device makes to its loopback-only bridge,
2813
+ and the supervisor forwards it to this Metro, so the host never connects back
2814
+ to this machine and a shared or ACL-restricted host works. For an Expo
2815
+ dev client the bridge listens on this machine's Metro port, which the dev server
2760
2816
  writes into the manifest. ios --remote starts Metro on a port free on both
2761
2817
  Macs; a running Metro or a pin on a port the host uses refuses the run before
2762
- the build. Port and secret
2763
- stay in 0600 workspace state and never appear in status, JSON or logs.
2764
- metro.tunnel and metro.publicUrl are ignored. Supervisor restart recreates
2765
- the recorded gateway; stop or a stopped hosted session closes it. A recorded
2818
+ the build. metro.tunnel and metro.publicUrl are ignored. Supervisor restart
2819
+ reopens the recorded links; stop or a stopped hosted session closes them. A recorded
2766
2820
  hosted session prevents Metro idle stop. Release runs skip the bridge.
2767
- Hosted Debug runs require the supervisor's private gateway support before
2768
- reservation. --no-metro-check refuses; restart a missing or older supervisor
2821
+ Hosted Debug runs require a supervisor and a stim-server that support these
2822
+ links before reservation. --no-metro-check refuses; restart a missing or older supervisor
2769
2823
  with stim stop; stim start.
2770
2824
  Native iOS and Android logs are pulled from the host by stim logs, including --errors;
2771
2825
  JavaScript logs still arrive through Metro. Stop pulls native logs before
@@ -2827,8 +2881,8 @@ BUNDLE WARMUP
2827
2881
  device logs may prove a request, but bundle completion may stay unverified.
2828
2882
 
2829
2883
  HOSTED ANDROID
2830
- android --remote <machine> keeps Metro here through the private tailnet
2831
- gateway. The host reverses the client Metro port into its loopback bridge
2884
+ android --remote <machine> keeps Metro here through the same client-opened
2885
+ links as hosted iOS. The host reverses the client Metro port into its loopback bridge
2832
2886
  on the exact owned emulator. Each run and reload restores that reverse;
2833
2887
  no adb command for the host serial runs here. metro.tunnel and metro.publicUrl
2834
2888
  are ignored. See guide lifecycle hosted-android.
@@ -3199,7 +3253,8 @@ FLAGS
3199
3253
  shared untagged Metro and app client records (not the web
3200
3254
  page's); default also includes untagged legacy records
3201
3255
  --source <s...> metro, client, device, build, agent, maintenance, placement (one or more), or all.
3202
- gc alone reads the machine-wide gc log (GC LOG below).
3256
+ gc alone reads the machine-wide gc log (GC LOG below);
3257
+ builds alone reads the machine-wide build ledger (BUILD LEDGER below).
3203
3258
  An unknown value is REJECTED rather than quietly matching
3204
3259
  nothing.
3205
3260
  --level <l> minimum level: debug, info, warn, error, fatal
@@ -3252,7 +3307,8 @@ serve bounded log queries.
3252
3307
  SecTrust, WebKit, CoreUI) that have nothing to do with your app. The proven
3253
3308
  ones are demoted to info by the collector, and iOS main-thread
3254
3309
  "Synchronous URL loading" performance diagnostics (Expo icons served by
3255
- Metro raise them) to warn, so a launch does not count them as app errors;
3310
+ Metro raise them) and SwiftUI "Invalid Configuration" faults (the Expo dev
3311
+ launcher raises them for a duplicated server entry) to warn, so a launch does not count them as app errors;
3256
3312
  the scope rule covers the rest.
3257
3313
  The metro stream carries exactly one demotion of its own, and it is Stim's
3258
3314
  doing: the dev-client deep link \`ios\`/\`android\` open to wire the app to
@@ -3370,6 +3426,35 @@ THE RECORD
3370
3426
  there are none. Stim adds it at query time; it is not in the
3371
3427
  log files.
3372
3428
 
3429
+ IOS BOOT RECORDS
3430
+ Booting an owned iOS simulator writes records to the workspace's device.ndjson:
3431
+ src device, platform ios, deviceId the simulator udid, slot when it is not
3432
+ default, level info unless noted. They show in a plain \`logs\` and in
3433
+ \`--source device\`, and never in \`--errors\`.
3434
+ event ios_boot, with phase:
3435
+ boot_requested just before simctl boot; boot is adopted (a parked
3436
+ simulator), created, existing (an owned simulator
3437
+ that was shut down) or restart
3438
+ bootstatus_done simctl bootstatus -b returned
3439
+ springboard_started SpringBoard finished starting; pid is its pid
3440
+ viewer_opened the simulator viewer was asked to open it
3441
+ simslim_reboot SimSlim changed the simulator and SpringBoard's pid
3442
+ changed; pidBefore and pidAfter are null when the pid
3443
+ could not be read
3444
+ restart warn: SpringBoard is not running after boot and the
3445
+ simulator was shut down; Stim boots it once more
3446
+ event springboard_crash_reports, written before that restart and again if
3447
+ SpringBoard is still missing after it. files lists the SpringBoard-*.ips
3448
+ reports that name this simulator and were captured in the last ten minutes,
3449
+ copied from ~/Library/Logs/DiagnosticReports and its Retired folder into
3450
+ the workspace logs directory; level is warn, or info with an empty files
3451
+ when none matched. dropped counts matching reports that exceeded the limits
3452
+ below and were not copied; failure is the message of a write that failed,
3453
+ with the reports copied before it in files. macOS deletes the originals
3454
+ after about two days; Stim only reads that folder. It looks at the newest
3455
+ 100 reports of at most 1 MiB each, copies at most 8 MiB per run, and keeps
3456
+ at most 40 in the directory.
3457
+
3373
3458
  Maintenance records use src: maintenance and appear in the plain timeline.
3374
3459
  stim logs --source maintenance
3375
3460
  Add --errors to --source maintenance to show only maintenance_failure events;
@@ -3397,6 +3482,51 @@ Each record has src: gc, level (info, or error for a failed item), msg and
3397
3482
  and the reason a kept or failed item was left
3398
3483
  Records of one run share runId. See stim guide cleanup, WHAT WAS CLEARED.
3399
3484
 
3485
+ BUILD LEDGER
3486
+ stim logs --source builds reads $STIM_HOME/builds/builds.ndjson from any
3487
+ directory: one record per native build run (ios, android or macos; compiled
3488
+ here, offloaded or fetched from a cache; succeeded, failed or cancelled). It
3489
+ keeps the newest 500 records, outlives the workspace that ran the build, and
3490
+ is never removed by gc. It cannot be combined with other sources, and --source
3491
+ all does not include it. --errors shows the failed runs. A record has
3492
+ src: builds, event: build, level (info, warn for cancelled, error for failed),
3493
+ msg and
3494
+ ts, startedAt epoch milliseconds when the run finished and started
3495
+ id the run
3496
+ project, workspace, branch
3497
+ project root, workspace id and the branch checked out
3498
+ platform ios, android or macos
3499
+ scheme, variant, configuration
3500
+ the iOS scheme when one was selected, the Android variant,
3501
+ the build configuration
3502
+ placement { where: local | offloaded, machine?, fellBack?, reason? };
3503
+ machine is the build machine that compiled the app, and
3504
+ fellBack with reason marks a build that considered
3505
+ offloading and compiled here
3506
+ cache { result: hit | miss | bypassed, source?: local | remote,
3507
+ key, fingerprint, missReason? }
3508
+ durationMs, phases
3509
+ wall time and milliseconds in each local phase (prepare,
3510
+ cache-lookup, wait, prebuild, pods, compile, device,
3511
+ install, launch)
3512
+ offloadPhases milliseconds on the build machine: sync, deps, prebuild,
3513
+ pods, build, fetch
3514
+ outcome succeeded, failed or cancelled
3515
+ failure failed or cancelled runs: { code, message, remedy, phase }
3516
+ device { name, runtime? } of the simulator, emulator or device;
3517
+ never its serial or UDID
3518
+ loadPerCore this machine's 5-minute load average per CPU when the run
3519
+ started, to one decimal
3520
+ boot { startedAt, loadPerCore } of the run's first owned
3521
+ simulator or emulator boot: epoch milliseconds when it
3522
+ started, once any concurrency.maxDevices wait was over,
3523
+ and the load per core then. Absent when the run booted
3524
+ nothing.
3525
+ stimVersion the stim that ran the build
3526
+ Records written before these fields existed lack loadPerCore and boot.
3527
+ A run that stopped before it had an app to build (a refused flag, a missing
3528
+ project) writes no record. Writing the record never changes the run's result.
3529
+
3400
3530
  REMOTE REQUEST RECORDS
3401
3531
  A request to another machine that fails is a warn record with src: build in the
3402
3532
  run's build log: remote_connect_failed { host, capability, ms, timeoutMs, msg,
@@ -3426,16 +3556,45 @@ Events: run_start, run_end { exit, ms }, exec { program, ms, ok, exit? }, and
3426
3556
  remote_connect and remote_request { ms, ok, code? } for requests to another
3427
3557
  machine. They carry no arguments, tokens or tickets.
3428
3558
 
3559
+ TIMING TRACES
3560
+ STIM_TRACE=<path> makes a stim command write a timeline of where it spent its
3561
+ time, as Chrome trace events. Open the file in Perfetto (ui.perfetto.dev) or
3562
+ chrome://tracing. Unset, nothing is written and no file is opened. There is no
3563
+ setting for it, and the trace goes only to that file, never to stdout or stderr.
3564
+ Use a new path for each command: the command you start truncates the file, and
3565
+ the processes it starts (a Metro supervisor, collectors, a nested stim) append
3566
+ to it.
3567
+ STIM_TRACE=/tmp/ios.json stim ios
3568
+ The file is a JSON array with one event per line and no closing bracket, so
3569
+ read it with JSON.parse(text.trimEnd().replace(/,$/, '') + ']'). Every event
3570
+ has pid; spans have ts and dur in microseconds since the epoch. Events:
3571
+ command one span named "stim <command>" with the exit code in args.exit
3572
+ exec one span per child process. args.argv (or args.command for a shell
3573
+ command) has the values of secret-looking flags, credentials in
3574
+ URLs and token shapes replaced by [redacted], and the credentials
3575
+ of curl -u and security import -P; other short flags such as -p
3576
+ and -u are kept. args also has cwd, ok, exit and signal
3577
+ phase the build phases of an ios, android or macos run on a
3578
+ "phases: <platform> build" track, named as in stats.json and the
3579
+ build ledger (prepare, cache-lookup, wait, prebuild, pods, compile,
3580
+ device, install, launch), and the steps of worktree warm
3581
+ span fingerprint, wait for metro and carry ignored entries
3582
+ mark instant events such as a fingerprint memo hit
3583
+ Children running at the same time are drawn on separate tracks. A span still
3584
+ open when its process exits is written with unfinished: true; a process ended
3585
+ by a signal writes none. A trace holds
3586
+ full paths and arguments, so treat it like a log.
3587
+
3429
3588
  Placement records use src: placement and sit in the run's build log
3430
3589
  (build-*.ndjson), so a new run replaces the previous run's. A run writes one
3431
3590
  record per decision it makes (a build that compiles and has a remote machine to
3432
- consider, or ios/android --remote auto), with event build_placement,
3591
+ consider, or ios/android --remote auto|remote|remote-only), with event build_placement,
3433
3592
  device_placement or placement_fallback. A cache hit, a project with no paired
3434
3593
  remote machine and a named --remote target write none:
3435
3594
  stim logs --source placement
3436
3595
  stim logs --source placement --json
3437
3596
  Fields: kind (build or device), platform, settings [{ key, value, from }] with
3438
- from flag, env, setting or default (remote.build and remote.buildMode for a
3597
+ from flag, env, setting or default (remote.build for a
3439
3598
  build, ios.remote or android.remote, plus remote.easFallback when it is on, for
3440
3599
  a device), candidates [{ machine, code,
3441
3600
  msg, detail? }], choice { machine, code, msg } with machine local when the run
@@ -3458,6 +3617,8 @@ WHAT WRITES WHAT
3458
3617
  maintenance.ndjson workspace maintenance actions, failures and explaining skips
3459
3618
  $STIM_HOME/gc/gc.ndjson machine-wide, not in a workspace: what gc, maintenance
3460
3619
  and the budget gate cleared (stim logs --source gc)
3620
+ $STIM_HOME/builds/builds.ndjson machine-wide: the last 500 native builds
3621
+ (stim logs --source builds)
3461
3622
  metro.ndjson the bundler, in every supervisor mode. Stim's
3462
3623
  middleware adds one record per bundle request
3463
3624
  (bundle_response_started, _progress, _finished,
@@ -3480,7 +3641,8 @@ WHAT WRITES WHAT
3480
3641
  -- and, on iOS, where every Apple framework running in
3481
3642
  the app's process also logs. The proven noise sources
3482
3643
  are recorded at info rather than error (Synchronous
3483
- URL loading diagnostics at warn); the rest is why
3644
+ URL loading diagnostics and SwiftUI Invalid Configuration
3645
+ faults at warn); the rest is why
3484
3646
  --errors leaves this source out unless asked. A VERIFIED
3485
3647
  LAUNCH counts these records and prints one line:
3486
3648
 
@@ -3673,7 +3835,12 @@ Branch on the code, never on the message.`,
3673
3835
  hosted session. Auto skips declined offers before building, then uses the
3674
3836
  local queue if none admits. A reserve refusal after an accepted offer fails
3675
3837
  with this code and asks to retry; it does not re-place the built architecture.
3676
- Check stim-server and Tailscale there,
3838
+ remote-only never runs here and never joins the local queue: it refuses
3839
+ with this code when no host could ever take the run, and waits for a host that
3840
+ is only full or loaded (see STIM_AT_CAPACITY).
3841
+ An unreachable host's message and remedy are the diagnosis stim doctor gives
3842
+ (Tailscale off, Mac offline with last seen, serve route, stim-server), without
3843
+ tailscale ping. Check stim-server and Tailscale there,
3677
3844
  then run stim doctor. Correct --device-type / --runtime for iOS or
3678
3845
  --system-image / --device-profile for Android when the offer names
3679
3846
  an unavailable choice. A session that exists stays recorded even if delivery
@@ -3795,9 +3962,14 @@ Branch on the code, never on the message.`,
3795
3962
  note naming which of the two set it; Gemfile.lock is never edited either way.
3796
3963
  \`worktree warm --refresh\` reports the same code for the install it runs in
3797
3964
  the SOURCE CHECKOUT -- the lockfile's own command (\`pnpm install\`,
3798
- \`yarn install\`, \`bun install\`, \`npm ci\`) or that same pod ladder. The
3965
+ \`yarn install\`, \`bun install\`, \`npm ci\`), the \`npm run postinstall\`
3966
+ it runs when npm's config there sets ignore-scripts, or that same pod ladder. The
3799
3967
  message names the command, quotes its last lines, and nothing is copied: fix
3800
- the source checkout, then warm again.`
3968
+ the source checkout, then warm again.
3969
+ start, ios and android also refuse with this code when \`gc --delete\`
3970
+ lightened the worktree and the \`stim worktree warm\` they ran to restore
3971
+ its node_modules and ios/Pods failed. The warm's own lines say why; fix
3972
+ that, then run \`stim worktree warm\` or retry the command.`
3801
3973
  },
3802
3974
  STIM_BUILD_FAILED: {
3803
3975
  summary: "xcodebuild or gradle failed; the Android missing-SDK and APK refusals",
@@ -4035,6 +4207,11 @@ and no enabled remote can finish this build, or when the build requires a local
4035
4207
  compiler. Enable local or choose an explicit --remote-build placement. Existing
4036
4208
  builds are not interrupted and a cache hit needs no compiler.
4037
4209
 
4210
+ remote-only never builds here: it refuses when no remote machine in remote.machines
4211
+ can take the build, naming each machine and why it was skipped, and for a build that
4212
+ cannot leave this machine. Rerun with --remote-build remote to allow a local build
4213
+ when no remote machine can take it.
4214
+
4038
4215
  A named --remote-build selection is strict. The message names the machine
4039
4216
  and why it cannot take or finish the build: not configured or paired, approval
4040
4217
  pending or denied, unreachable, changed pinned server key, a stim-server
@@ -4063,7 +4240,7 @@ Run stim doctor --fix to ask for build access if not paired. Check
4063
4240
  stim settings get remote.machines. A person on the worker approves this machine
4064
4241
  by running the stim-server setup command doctor --fix printed, with its ticket
4065
4242
  and expiry (guide settings). Agents never run setup or approve requests.
4066
- Rerun with --remote-build auto for normal placement and local fallback, or
4243
+ Rerun with --remote-build auto or remote for placement with local fallback, or
4067
4244
  --remote-build local to keep the build here.
4068
4245
  `
4069
4246
  },
@@ -4158,6 +4335,13 @@ Rerun with --remote-build auto for normal placement and local fallback, or
4158
4335
  Trust, then run the command again. It is a per-developer-certificate tap, not
4159
4336
  a per-build one -- but an uninstall clears it, including the one Stim's own
4160
4337
  signer-conflict retry performs.
4338
+ On a local iOS simulator, a cache-hit app that dyld kills at launch because
4339
+ one part of the app cannot bind a symbol from another part of the same app
4340
+ (for example React.framework against hermesvm.framework) is a damaged build,
4341
+ not a launch problem. Stim removes that cache entry, and the remedy names
4342
+ it. Run the command again to build the app fresh. A cache provider's copy is
4343
+ not removed; add \`--no-build-cache\` to skip it. If the fresh app crashes
4344
+ the same way, the project's own native code is the cause.
4161
4345
  With \`--remote eas\` the remedy names the EAS Simulator session that is
4162
4346
  still running, as for STIM_INSTALL_FAILED.`
4163
4347
  },
@@ -4439,6 +4623,16 @@ so a Debug run on one is wired to a LAN origin instead of localhost.`,
4439
4623
  installation. If it fails, the refusal includes observed host memory pressure
4440
4624
  when available. Free memory before retrying under pressure; see
4441
4625
  \`stim guide lifecycle simslim\`. Booted alone does not prove readiness.
4626
+ The same probe lists the simulator's launchd jobs and requires a running
4627
+ SpringBoard. A simulator that reports Booted while SpringBoard has died
4628
+ (\`simctl launch\` then says "The system shell probably crashed") is shut
4629
+ down through teardown and booted once more; if SpringBoard is still not
4630
+ running, the refusal names the udid and the remedy: \`stim stop\`, then
4631
+ \`stim ios\` again. Repeated SpringBoard crashes leave reports in
4632
+ ~/Library/Logs/DiagnosticReports, and macOS deletes them after about two
4633
+ days; before the restart Stim copies the simulator's recent ones into the
4634
+ workspace logs directory and records the boot steps by udid
4635
+ (\`stim logs --source device\`, \`stim guide logs\`, IOS BOOT RECORDS).
4442
4636
  On Android the emulator's own stdio is captured to
4443
4637
  the global workspace logs/emulator.log (truncated per boot), and when it printed a
4444
4638
  \`FATAL |\` / \`ERROR |\` / \`PANIC:\` line THAT is the message and the remedy
@@ -4541,6 +4735,11 @@ so a Debug run on one is wired to a LAN origin instead of localhost.`,
4541
4735
  locks, builds, viewers and recent activity. Reclaim excludes this workspace,
4542
4736
  physical, hosted, remote, parked and other homes' devices and never deletes.
4543
4737
  A failed reclaim is logged and skipped; the run keeps waiting.
4738
+ --remote remote-only (and macos.remote remote-only) has no local queue: when every
4739
+ approved host is full, loaded or short of memory it polls the hosts every 5 seconds
4740
+ for the same wait (--wait, default 600 seconds) and then refuses with this code;
4741
+ --no-wait and --wait 0 refuse at once. Builds under remote-only refuse with
4742
+ STIM_OFFLOAD_REFUSED instead.
4544
4743
  Stop an environment (\`stim stop\`), pass a longer \`--wait <seconds>\`,
4545
4744
  or raise concurrency.maxDevices. With --remote auto, remote.machines and
4546
4745
  the opt-in remote.easFallback (billed EAS Simulator) are tried before this
@@ -4980,8 +5179,8 @@ Inspect what remains with gc() before retrying a mutation.
4980
5179
  `
4981
5180
  },
4982
5181
  STIM_CANCELLED: {
4983
- summary: "an ios or android run was interrupted by `stim stop` or Ctrl-C before it finished",
4984
- body: () => `STIM_CANCELLED (ios, android; exit 130)
5182
+ summary: "an ios, android or macos run was interrupted by `stim stop` or Ctrl-C before it finished",
5183
+ body: () => `STIM_CANCELLED (ios, android, macos; exit 130)
4985
5184
  "The ios run was cancelled by \`stim stop\` (pid <n>) before it finished."
4986
5185
  The run was interrupted: by \`stim stop\`, which cancels a build it leaves
4987
5186
  with nothing to deploy to, or by Ctrl-C. It forwarded the interrupt
@@ -4990,8 +5189,11 @@ Inspect what remains with gc() before retrying a mutation.
4990
5189
  stopped at. An interrupted build stores no artifact, so the next run looks
4991
5190
  up the cache as usual. Stopped between build tools, a run that \`stop\`
4992
5191
  cancelled starts no further build tool and stops before it installs. A
4993
- second Ctrl-C exits at once, and so does a Ctrl-C with no build tool or
4994
- SimSlim to stop; those exits print no payload. Run the same command again when you want the
5192
+ Ctrl-C while the run delivers the app to a hosting Mac, or waits for the app
5193
+ to load its bundle or report readiness, ends that work and reports
5194
+ STIM_CANCELLED. A second Ctrl-C exits at once, and so does a Ctrl-C with no
5195
+ build tool, SimSlim, hosted app delivery or launch readiness wait to stop;
5196
+ those exits print no payload. Run the same command again when you want the
4995
5197
  app on a device.`
4996
5198
  },
4997
5199
  STIM_STOP_BLOCKED: {
@@ -5204,8 +5406,9 @@ not on any remote" (worktree remove)
5204
5406
  lockfile says nothing about the one on disk now, whose dependencies may well
5205
5407
  have been installed since; and a repository with no record at all -- every
5206
5408
  repository before its first \`--refresh\` -- copies as it always has.
5207
- In a monorepo the record is keyed on the directory that owns the lockfile,
5208
- which is usually the repository root, so every app of it reads the same one.`
5409
+ Each directory that owns a lockfile the refresh installs has its own record:
5410
+ the repository root and, when the app has a lockfile of its own, the app. A
5411
+ plain warm reads both.`
5209
5412
  },
5210
5413
  warm: {
5211
5414
  summary: "worktree warm claim storage refusals and recovery",
@@ -5494,7 +5697,7 @@ STIM_SUPERVISOR_EXITED, ...) before any build work. Release builds and
5494
5697
  \`--no-metro-check\` neither check nor start the dev server; hosted Debug iOS
5495
5698
  refuses --no-metro-check with STIM_BAD_ARG. \`--remote\`
5496
5699
  with eas or proxy starts it the way \`stim start --remote\` does. A named
5497
- hosting Mac keeps Metro local through a private bridge; see hosted-ios. Step 2 remains useful to warm
5700
+ hosting Mac keeps Metro local through links this machine opens; see hosted-ios. Step 2 remains useful to warm
5498
5701
  Metro while other work happens.
5499
5702
 
5500
5703
  Each time Stim boots an owned local simulator or emulator, it mutes it: iOS
@@ -5674,7 +5877,8 @@ WAITING FOR A CHANGE
5674
5877
  status payload per line: one at once, then one each time the payload
5675
5878
  changes, never two identical ones in a row. In a watch line, a usage
5676
5879
  figure repeats its value in the previous line until it moves by a step: an owner's cpuPercent 5 points, an
5677
- owner's or environment's memoryMb and capacity.committedMb 16 MB. An owner's row
5880
+ owner's or environment's memoryMb and capacity.committedMb 16 MB, and
5881
+ capacity.load as a whole until its loadPerCore moves 1 per core or its state changes. An owner's row
5678
5882
  changes as a whole, so its residentMb and processes update with it. A change
5679
5883
  in only a bookkeeping timestamp prints no line: each pullRequest.checkedAt,
5680
5884
  maintenance.lastChecks, maintenance.lastPass.startedAt and durationMs, and
@@ -5901,9 +6105,9 @@ If Stim is not installed globally, replace stim with npx stim.
5901
6105
 
5902
6106
  A person on the host approves device-host access, separately from build, read
5903
6107
  and control. The setup in hosted-ios also serves Android. android.remote sets
5904
- a workspace default. eas and proxy remain backends. --remote auto or
5905
- android.remote = auto places automatically; see hosted-ios for the shared
5906
- policy. No flag or setting runs on this machine.
6108
+ a workspace default. eas and proxy remain backends. --remote auto, remote or
6109
+ remote-only (or the same android.remote value) places automatically; see
6110
+ hosted-ios for the shared policy. No flag or setting runs on this machine.
5907
6111
 
5908
6112
  A named Mac is strict: refusal, unreachable, declined or failed preparation
5909
6113
  returns STIM_HOSTING_REFUSED without fallback. --device with --remote
@@ -5920,8 +6124,8 @@ soon as it exists, then delivers one App.apk. The emulator boots headless;
5920
6124
  androidEmulatorApp is ignored with a note.
5921
6125
 
5922
6126
  React Native Debug requires the local Metro supervisor. metro.publicUrl and metro.tunnel
5923
- are ignored with a note. A private tailnet gateway reaches the host loopback
5924
- bridge; the host reverses the client's Metro port into that bridge on its exact
6127
+ are ignored with a note. Connections this machine opens to the host carry each
6128
+ device connection from the host loopback bridge to local Metro; the host reverses the client's Metro port into that bridge on its exact
5925
6129
  ledger-owned serial and sets debug_http_host to localhost:<clientMetroPort>.
5926
6130
  Each run and stim reload android restores the reverse, including after a host
5927
6131
  adb server restart. Reload broadcasts through the local Metro and never runs
@@ -5944,7 +6148,7 @@ session probe states; a conflicting local emulator remains visible with a
5944
6148
  warning. A recorded placement keeps the workspace in use and blocks idle stop.
5945
6149
 
5946
6150
  stop and worktree remove wait for device-host.stop, park the host's eligible owned
5947
- emulator within pool.androidParkedMax (delete otherwise), close the gateway and clear placement. stop --json reports
6151
+ emulator within pool.androidParkedMax (delete otherwise), close the Metro links and clear placement. stop --json reports
5948
6152
  outcomes.device["android:host:<slot>"]. Forbidden or unknown-session also clears
5949
6153
  placement; an unreachable host keeps it and fails cleanup. A clean host restart
5950
6154
  parks eligible devices; a later reserve by the same client adopts a compatible
@@ -6030,8 +6234,8 @@ Logcat retains a finite buffer; records already evicted or persisted beyond the
6030
6234
  overlap may be unavailable. An oversized query retries the recent tail and writes
6031
6235
  a device warning naming any dropped interval. Followers share a child-aware log
6032
6236
  claim; install and stop cancel and settle the worker before native operations.
6033
- Stop copies logs before and after the host's final collection and teardown, with
6034
- each client drain bounded to 30 seconds and progress on stderr. Collected logs
6237
+ Stop copies logs once, after the host's final collection and teardown, bounded to
6238
+ 30 seconds with progress on stderr; Ctrl-C cancels the copy and keeps the placement. Collected logs
6035
6239
  remain readable after stop. The host advertises hosted-android-data for Android
6036
6240
  handoff and native logs. Older hosts get a newer stim-server note, use upload
6037
6241
  and show records already copied here.`
@@ -6051,28 +6255,44 @@ If Stim is not installed globally, replace stim with npx stim.
6051
6255
  stim reload ios
6052
6256
  stim stop
6053
6257
 
6054
- The same setup serves macos --remote. Approval is separate from build, read
6258
+ The same setup serves macos --remote and macos.remote. Approval is separate from build, read
6055
6259
  and phone control. Set ios.remote to the machine name for a default (machine scope for every app on this machine);
6056
6260
  no flag or setting runs here. eas and proxy keep their remote backend meanings.
6057
6261
  --remote auto (or ios.remote = auto / android.remote = auto) stays here when
6058
6262
  there is a free concurrency.maxDevices slot (0 means unlimited), no device
6059
6263
  waiter ahead, normal host memory pressure, no budget shortfall,
6060
6264
  and 5-minute load per core below server.maxLoadPerCore. Without remote.machines,
6061
- auto is local. An unknown local device count also stays local; boot admission
6265
+ auto and remote are local and remote-only refuses with STIM_HOSTING_REFUSED. An unknown local device count also stays local; boot admission
6062
6266
  still decides. The default without a flag or setting stays local. remote.devicePoolDisabled excludes
6063
6267
  members from new automatic placement only; an excluded local member is never a
6064
6268
  fallback. Existing sessions keep their owner and named placement bypasses the pool.
6065
6269
  See stim guide settings for separate build/device membership controls.
6066
6270
 
6271
+ --remote remote (or ios.remote = remote) skips that local check and asks the hosts
6272
+ first, in remote.machines order; the simulator runs here only when none admits
6273
+ it, with the same wait and EAS fallback as auto. --remote remote-only never runs
6274
+ here and never joins the local queue or uses EAS. When every host that answered is
6275
+ only full, loaded or short of memory, it waits for one to have room, polling every
6276
+ 5 seconds for --wait seconds (default 600) and printing one placement line; --no-wait,
6277
+ --wait 0 or the end of the wait refuse with STIM_AT_CAPACITY. When no host can ever take
6278
+ the run (none configured, unreachable, declined for another reason such as paused),
6279
+ it refuses at once with STIM_HOSTING_REFUSED, naming each skipped host and its reason.
6280
+ --plan never waits. It prints the same STIM_HOSTING_REFUSED, or a STIM_BAD_ARG naming
6281
+ an invalid remote.build or STIM_REMOTE_BUILD.
6282
+ Excluding local in remote.devicePoolDisabled has the same effect on auto and remote.
6283
+
6067
6284
  Otherwise Stim asks every approved remote.machines host in parallel, skipping
6068
6285
  one that tailscale status reports as running an OS other than macOS, with a
6069
6286
  3-second probe timeout and this run's model/runtime or image/profile selectors.
6070
- A compatible choice, no declined reason, normal memory pressure and available
6071
- capacity are required. With a free local slot, a host must report a lower load;
6072
- with a full cap or a queue ahead, an admitted host with unknown load is allowed.
6073
- Hosts rank by the build preference (flag > STIM_REMOTE_BUILD > remote.build)
6074
- when it names a machine, then lowest load, most free memory and remote.machines order. Automatic build
6075
- offload resolves later, after the device architecture is known.
6287
+ A compatible choice, no declined reason, normal memory pressure, available
6288
+ capacity and a load per core under the host's own server.maxLoadPerCore are
6289
+ required (a host that reports no limit uses this machine's). With a free local
6290
+ slot, a host with unknown load is skipped under auto (remote admits it); with a full cap or a queue ahead, an
6291
+ admitted host with unknown load is allowed. Admitted hosts are tried in order:
6292
+ the host named by the build preference (flag > STIM_REMOTE_BUILD > remote.build)
6293
+ when it names a machine, then remote.machines order, so the first listed host with
6294
+ room fills before the next gets a run. Load and free memory do not rank hosts.
6295
+ Automatic build offload resolves later, after the device architecture is known.
6076
6296
 
6077
6297
  When no host admits and this machine is at its concurrency.maxDevices cap (or runs
6078
6298
  are queued ahead), remote.easFallback (default false; EAS Simulator is billed)
@@ -6089,7 +6309,7 @@ metro.tunnel off; on an Expo tunnel, run stim start --remote first). A recorded
6089
6309
  EAS session is not sticky: once this machine has room, auto runs here and the
6090
6310
  session bills until stim stop or remote.easMaxDurationMinutes. If any check fails, or the
6091
6311
  setting is off, auto runs here and may wait in the existing FIFO device
6092
- slot queue; --no-wait and --wait 0 refuse with STIM_AT_CAPACITY. The placement
6312
+ 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
6093
6313
  line explains the decision, the skipped hosts and why EAS was not used
6094
6314
  (machine "eas"). JSON progress goes to stderr.
6095
6315
  The same decision, with a reason code per host, is a src: placement record in
@@ -6101,8 +6321,8 @@ A reserve refusal after a successful offer can race with another run. It fails
6101
6321
  with STIM_HOSTING_REFUSED naming the host and asking to retry. Stim does not
6102
6322
  try the next host or local placement after the build has targeted that host's
6103
6323
  architecture, and never falls back once a hosted session exists. A named machine remains strict. --device
6104
- cannot combine with --remote auto, and a --device run ignores ios.remote and
6105
- android.remote. eas/proxy and macOS placement are unchanged.
6324
+ cannot combine with --remote auto, remote or remote-only, and a --device run ignores ios.remote and
6325
+ android.remote. eas/proxy placement is unchanged; macOS placement follows macos.remote (stim guide macos).
6106
6326
  --simulator-app is honoured locally with auto and ignored with a note on a host.
6107
6327
  Placement measures budgets without reclaiming; only a new local run reclaims.
6108
6328
  Android --remote <machine> uses an owned emulator on that Mac; see hosted-android.
@@ -6134,9 +6354,10 @@ sessions.
6134
6354
 
6135
6355
 
6136
6356
  Stim builds or fetches before reserving, records the session immediately, then
6137
- uploads the app on every run. React Native and Expo Debug keep Metro here; the supervisor owns a
6138
- private gateway bound to this machine's Tailscale address, pinned to the host peer
6139
- and protected by a per-session secret. The host exposes only a loopback bridge.
6357
+ uploads the app on every run. React Native and Expo Debug keep Metro here; the supervisor keeps
6358
+ authenticated connections open to the host, and the host pairs one with each
6359
+ connection the simulator makes to its loopback-only bridge. The host never
6360
+ connects back to this machine.
6140
6361
  For an Expo dev client the bridge listens on this machine's Metro port, because
6141
6362
  the dev server writes that port into the manifest's bundle URL. When this run
6142
6363
  starts Metro, it asks the host and picks a port free on both Macs, moving the
@@ -6144,8 +6365,8 @@ recorded port if the host uses it. A running Metro or a metro.port pin on a port
6144
6365
  the host uses refuses before the build: run stim stop, then stim ios --remote
6145
6366
  <machine> without stim start, or pin a port free on both Macs.
6146
6367
  No start --remote is needed; metro.tunnel and metro.publicUrl are ignored.
6147
- Debug requires a running supervisor with private gateway support before reserving
6148
- a simulator. --no-metro-check refuses with STIM_BAD_ARG for hosted Debug runs.
6368
+ Debug requires a running supervisor and a host stim-server that support these
6369
+ links before reserving a simulator. --no-metro-check refuses with STIM_BAD_ARG for hosted Debug runs.
6149
6370
  If its supervisor is missing or older, run stim stop; stim start, then retry.
6150
6371
  Non-Debug runs skip Metro. Launch is unverified until this workspace's Metro
6151
6372
  provides bundle evidence, or the host proves a live release process. Native Xcode
@@ -6158,14 +6379,14 @@ hosting Mac, refuses for that slot: run stim stop first.
6158
6379
  Status adds ios.host { machine, session, selected, device: { name, runtime },
6159
6380
  agent, state } per slot. A shutdown local simulator is replaced in status; a
6160
6381
  booted or unknown local simulator stays visible alongside ios.host with a warning.
6161
- The default slot appears only in ios, never in slots[]. No host UDID or gateway secret enters local device state or
6382
+ The default slot appears only in ios, never in slots[]. No host UDID enters local device state or
6162
6383
  status. Ready is normal; stopped has a rerun hint; unknown or unreachable is
6163
6384
  unverified. A clean host server restart stops its sessions and retains eligible
6164
6385
  parked devices. A running session left by a crash stays unknown until explicit
6165
6386
  stop; the restarted server never re-attaches to a booted device.
6166
6387
 
6167
6388
  stop and worktree remove wait for the host to stop, park its eligible owned
6168
- simulator within pool.iosParkedMax (delete otherwise), close the gateway and clear placement. stop --json
6389
+ simulator within pool.iosParkedMax (delete otherwise), close the Metro links and clear placement. stop --json
6169
6390
  reports each hosted slot under outcomes.device["ios:host:<slot>"], retaining
6170
6391
  local device outcomes and successful siblings when another slot fails. Revoked or
6171
6392
  missing sessions clear placement too. An unreachable host retains placement
@@ -7444,8 +7665,10 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
7444
7665
  Waiting prints the count, holder workspace names
7445
7666
  and elapsed time at once and about every 10s, then
7446
7667
  the queue position, head workspace name and
7447
- $STIM_HOME/device-waits path. Timeout names the
7448
- same head and path. Stop cancels a queued run and
7668
+ $STIM_HOME/device-waits path. Workspaces that
7669
+ share a project name show the directory that tells
7670
+ them apart, as in mobile (qa-b-p2). Timeout names
7671
+ the same head and path. Stop cancels a queued run and
7449
7672
  releases its ticket without booting its device.
7450
7673
  Status JSON adds build.waitingFor independently
7451
7674
  of phase: { kind: "device-slot" | "build-slot",
@@ -7523,7 +7746,14 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
7523
7746
  (\`gc --delete\` semantics; the next build installs from the shared
7524
7747
  cache). A workspace whose last use is unknown is kept
7525
7748
  4. trim shared cache entries nothing has used for 14 days
7526
- (\`gc --delete --older-than 14\` for the caches)
7749
+ (\`gc --delete --older-than 14\` for the caches). Caches with nothing
7750
+ to trim print one line, not one each. A trim that removed nothing is
7751
+ recorded in $STIM_HOME/budget-sweep.json and not repeated for 5
7752
+ minutes while the set of caches is the same and free disk has not
7753
+ fallen by more than 1 GB since; the run prints one budget line saying
7754
+ it skipped. \`gc\` and automatic maintenance never skip, and a dry
7755
+ run (\`doctor\`) still plans the step. The STIM_LOW_DISK message
7756
+ measures the largest uses of disk at the same time.
7527
7757
 
7528
7758
  IDLE SHUTDOWN (30 MINUTES BY DEFAULT): with devices.idleShutdownMinutes above
7529
7759
  0, a workspace's supervisor shuts down that workspace's owned simulators and
@@ -7592,8 +7822,8 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
7592
7822
  summary: "every flag per command, Android variants and flavors, the per-run simulator model, runtime and system image",
7593
7823
  body: () => `THE OPTION SURFACE, IN FULL
7594
7824
  start --json --wait <seconds> --remote --reset-cache
7595
- 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>
7596
- 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>
7825
+ 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>
7826
+ 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>
7597
7827
  ios/android --wait bounds owned-device slot waits (default 600s), or physical
7598
7828
  leases with --device (default 60s). Remote targets do not join this local
7599
7829
  slot queue. --no-wait refuses a full/queued owned
@@ -7701,12 +7931,24 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
7701
7931
  .stim.json, else \`git symbolic-ref --short refs/remotes/origin/HEAD\`;
7702
7932
  when neither answers it says so and makes no warning.
7703
7933
 
7704
- Dependencies install where the lockfile is, which in a monorepo is the
7705
- repository root, when the lockfile moved or nothing is installed. Pods run
7706
- for the app the command was invoked from, and only that app, when its
7707
- ios/Podfile.lock moved or ios/Pods/Manifest.lock does not match it. Each
7708
- completed or skipped deps and pods step names its source directory and
7709
- reason. A failed install refuses with STIM_DEPS_FAILED and nothing is copied.
7934
+ The app is the one warm prepares: the app the command was invoked from, or,
7935
+ from a monorepo root, the one app registered in the source checkout or in
7936
+ the worktree. With no such app, a refresh from the root updates only the
7937
+ repository root and says so on stderr.
7938
+ Dependencies install where the repository root's lockfile is and, when the
7939
+ app has its own lockfile (a nested package outside the root workspaces),
7940
+ there too; a root that has neither installed dependencies nor an install
7941
+ record is left alone when the app has its own lockfile. Each installs when
7942
+ its lockfile moved, when its patches/ directory moved (npm projects only),
7943
+ when nothing is installed, or when npm reports a tree that differs from the
7944
+ one it reported after the last completed install. An npm project whose
7945
+ npm config sets ignore-scripts gets no postinstall from \`npm ci\`, so when its
7946
+ package.json defines one the refresh runs \`npm run postinstall\` next
7947
+ (patch-package, usually); dependencies' own install scripts stay blocked.
7948
+ Pods run for that app, and only that app, when its ios/Podfile.lock moved or
7949
+ ios/Pods/Manifest.lock does not match it. Each completed or skipped deps and
7950
+ pods step names its source directory and reason. A failed install or
7951
+ postinstall refuses with STIM_DEPS_FAILED and nothing is copied.
7710
7952
 
7711
7953
  An install that did not finish is remembered, and a PLAIN warm reads that
7712
7954
  before it copies. The refresh records the completed install of the lockfile it
@@ -7771,7 +8013,11 @@ OPT-IN CONCURRENCY LIMITS (UNLIMITED BY DEFAULT)
7771
8013
  expo-modules-jsi/apple/.DerivedData Xcode build state, directly under
7772
8014
  node_modules or in the pnpm virtual store. An ignored directory that Git
7773
8015
  reports above those names (for example an app directory with no tracked
7774
- files) is copied as is. Warm also skips paths
8016
+ files) is copied as is. Warm renames those deleted paths into
8017
+ $STIM_HOME/trash and a detached, low-priority rm deletes them after warm
8018
+ returns (through the programmatic API, before the call resolves); when the
8019
+ rename is not possible (Windows, $STIM_HOME on another volume), warm deletes
8020
+ them before it returns. Warm also skips paths
7775
8021
  overlapping a nested destination worktree or below a symlink ancestor.
7776
8022
  Tracked .idea settings come from Git and stay untouched by warm.
7777
8023
 
@@ -8457,11 +8703,14 @@ In on mode a pass runs, cheapest to rebuild first:
8457
8703
  claim is unresolved; the emptying holds the exclusive claim
8458
8704
  5. linked worktrees whose branch or pull request finished (gc's finished
8459
8705
  worktree rules and gc.worktreeGraceMinutes; maintenance.removeFinishedWorktrees),
8460
- and, with maintenance.removeIdleWorktrees (off by default), in the age
8706
+ a dirty finished one after archiving its uncommitted files (ARCHIVED
8707
+ CHANGES in stim guide cleanup gc), clean ones with no commits of their
8708
+ own (NO COMMITS OF ITS OWN there), and, with
8709
+ maintenance.removeIdleWorktrees (off by default), in the age
8461
8710
  sweep clean, pushed ones unused for olderThanDays, as gc --delete
8462
8711
  --worktrees --older-than removes them, git-ignored files included; never
8463
- a dirty one, one with unpushed commits, the source checkout, or one whose
8464
- last use is unknown
8712
+ an unfinished dirty one, one with unpushed commits, the source checkout,
8713
+ a locked one, or one whose last use is unknown
8465
8714
  6. parked simulators and emulators unused for olderThanDays in the age
8466
8715
  sweep, through centralized teardown with ownership checked again
8467
8716
  7. owned simulators and emulators of a finished worktree that is kept, such
@@ -8497,14 +8746,21 @@ DISK PRESSURE LEVELS
8497
8746
  level 2, which runs until free disk is back above the floor; level 3 runs
8498
8747
  only while free disk stays under the level 3 floor. At levels 2 and 3 the
8499
8748
  protection window is maintenance.pressureProtectMinutes (10) instead of
8500
- protectRecentHours. Each step re-measures first and stops once free disk is
8501
- back above its level's floor, cheapest to rebuild first.
8749
+ protectRecentHours, except for the build outputs and build directories of
8750
+ level 2, which follow the lightening rule of gc --delete (WORKSPACE BUILD
8751
+ OUTPUTS): a running dev server, a booted or hosted device and a managed
8752
+ tunnel do not keep them, a build or a running macOS app does, and they stay
8753
+ for 15 minutes after the workspace's last ios, android or macos run.
8754
+ Each step re-measures first and stops once free disk is back above its
8755
+ level's floor, cheapest to rebuild first.
8502
8756
  Level 2:
8503
- 1. build outputs of workspaces unused for the short window
8757
+ 1. build outputs of workspaces, with the lightening rule above
8504
8758
  2. parked simulators and emulators, deleted through centralized teardown
8505
- 3. build directories of idle worktrees: ios/build, android/app/build,
8506
- android/build, android/.gradle and .build, at a project root, the
8507
- worktree root, or apps/* and packages/*
8759
+ 3. build directories of worktrees with a Stim workspace, by the same
8760
+ rule: android/app/build, android/build, android/.gradle and .build, at
8761
+ a project root, the worktree root, or apps/* and packages/*. ios/build
8762
+ stays: React Native's pod install writes the codegen output and
8763
+ podspec facades there that only pod install recreates
8508
8764
  4. owned simulators and emulators of workspaces idle for the short window
8509
8765
  5. Stim temp directories, xctrace recordings and SwiftPM temp directories
8510
8766
  in TMPDIR unmodified for 24 hours, with gc --delete's checks (see stim guide cleanup gc)
@@ -8514,12 +8770,14 @@ DISK PRESSURE LEVELS
8514
8770
  and the build
8515
8771
  2. finished worktrees, and clean, pushed worktrees with no commits of
8516
8772
  their own beyond the default branch, with the short window as grace
8517
- period (maintenance.removeFinishedWorktrees still applies)
8773
+ period and idle window (maintenance.removeFinishedWorktrees still applies)
8518
8774
  3. workspaces whose dev server runs but that are otherwise inactive for
8519
8775
  the short window: their dev server stops and their owned simulators
8520
8776
  and emulators are deleted
8521
8777
  4. node_modules and ios/Pods at the root or a project root of idle linked
8522
- worktrees, never the source checkout
8778
+ worktrees, never the source checkout, with the running-process checks
8779
+ of WORKTREE DEPENDENCIES; when worktree warm can restore them they are
8780
+ recorded for the next start, ios or android to restore
8523
8781
  5. idle Gradle and Kotlin daemons, stopped as gc --delete --cache
8524
8782
  gradle-daemons stops them (never during a Stim build, never a busy or
8525
8783
  unverified one, never with STIM_HOME set), only when every Gradle
@@ -8531,9 +8789,12 @@ DISK PRESSURE LEVELS
8531
8789
  gradle-versions, run again after step 5 so a daemon that did not exit
8532
8790
  still keeps them
8533
8791
  A directory is cleared only when it is a real directory inside its
8534
- worktree, git tracks nothing in it, its worktree is not in use, holds no
8535
- lock or build and is not pinned, and nothing under it, nor the worktree's
8536
- git index, HEAD or Stim state, changed within the short window. A dirty or
8792
+ worktree, git tracks nothing in it, its worktree is not in use, is not
8793
+ locked with git worktree lock, holds no build lock and is not pinned, and, for the node_modules and ios/Pods of
8794
+ level 3, nothing under it, nor the worktree's git index, HEAD or Stim
8795
+ state, changed within the short window. A build directory of level 2 also
8796
+ stays in a worktree where no Stim workspace has recorded any state; a build
8797
+ that Stim did not run is not detected. A dirty or
8537
8798
  unpushed worktree can still be stopped and have these directories cleared;
8538
8799
  only its removal is refused. Caches of other tools (pnpm and npm stores,
8539
8800
  CocoaPods, SwiftPM, the shared Gradle caches) are never deleted.
@@ -8664,6 +8925,11 @@ this cleanup. agent-device is optional: a missing binary skips this step, and a
8664
8925
  failed or timed-out list/close prints a device line on stderr while teardown
8665
8926
  continues. Each cleanup pass allows at most 15 seconds of agent-device calls
8666
8927
  per device; the pass \`stop\` makes after shutdown has its own budget.
8928
+ A daemon agent-device will not replace (daemon-busy: it is starting up or still
8929
+ has sessions, requests or leases) is not retried. Stim prints one line per
8930
+ device, naming each busy daemon, even though it lists sessions before and after
8931
+ shutdown: sessions on the device in them stay open until the daemon is idle or,
8932
+ if it is yours, stopped with agent-device daemon stop --state-dir <dir>.
8667
8933
  A local daemon that cannot use socket transport or a CLI without the target guard
8668
8934
  skips cleanup with a warning; no remote daemon is used.
8669
8935
 
@@ -8854,7 +9120,10 @@ LINKED WORKTREES
8854
9120
  still exists, as after a removal killed between the rename and git, stays
8855
9121
  with the reason; if that worktree is the one the entry came from, run
8856
9122
  \`stim worktree remove\` on it to finish the removal.
8857
- gc never moves the directories back.
9123
+ gc never moves the directories back. Warm uses the same trash for the
9124
+ known paths it deletes from a copy (such as a copied .pnpm-store); gc
9125
+ --delete deletes such an entry (trash.json kind warm) once its claim is free,
9126
+ whatever the state of the worktree.
8858
9127
  When git still refuses the removal, the kept ownership record no longer
8859
9128
  names the devices that were parked or deleted. A workspace that is in use
8860
9129
  (see IN USE below) keeps its directory, devices and registry entry, and the
@@ -8863,11 +9132,14 @@ LINKED WORKTREES
8863
9132
  only when it has no unique commits.
8864
9133
 
8865
9134
  IN USE
8866
- gc and worktree remove never delete a workspace's directory, build outputs
8867
- or checkout while it is in use: its dev server supervisor is running or
9135
+ gc and worktree remove never delete a workspace's directory or checkout
9136
+ while it is in use: its dev server supervisor is running or
8868
9137
  cannot be verified, a \`stim ios\`, \`stim android\` or \`stim stop\` run
8869
9138
  holds its native-run.lock, a live or unresolvable build lock or slot names it,
8870
- or its managed tunnel or managed remote lock is held. The deletion holds
9139
+ or its managed tunnel or managed remote lock is held. Build outputs follow
9140
+ the narrower rule in WORKSPACE BUILD OUTPUTS (\`guide cleanup disk\`): only
9141
+ a build, a running macOS app, or an ios, android or macos run in the last
9142
+ 15 minutes keeps them. The deletion holds
8871
9143
  native-run.lock itself, so no native run can start partway through. A lock
8872
9144
  that names no workspace blocks nothing, because every build also holds its
8873
9145
  own workspace's native-run.lock; gc lists it with the command that removes
@@ -8884,14 +9156,17 @@ SWEEPING FINISHED WORKTREES
8884
9156
  ever ran in it. It groups them by git worktree and reports each linked
8885
9157
  worktree with the reason it is removed or kept. A worktree is finished when
8886
9158
  its branch is merged into the default branch, or its pull request was
8887
- merged or closed (PULL REQUESTS below). \`--worktrees\` also removes
9159
+ merged or closed (PULL REQUESTS below). A clean worktree with no commits
9160
+ of its own counts as finished once idle (NO COMMITS OF ITS OWN below).
9161
+ \`--worktrees\` also removes
8888
9162
  one that is idle: no recorded use for --older-than days, 7 without it; a
8889
9163
  worktree whose last use is unknown is kept, unless it has no commits of its
8890
- own beyond the default branch (NO COMMITS OF ITS OWN below). Both need the
8891
- same clean state;
9164
+ own beyond the default branch (NO COMMITS OF ITS OWN below). Each needs a
9165
+ clean state, except a finished one whose changes are archived;
8892
9166
  a worktree is kept when it is the source checkout, bare, locked, holds
8893
9167
  another worktree (worktree remove refuses it too), in use,
8894
- dirty (untracked files count; pod install churn and
9168
+ dirty and not finished, or dirty and finished when its changes cannot be
9169
+ archived (ARCHIVED CHANGES below; untracked files count; pod install churn and
8895
9170
  watchman's untracked \`.watchman-cookie-*\` files alone do not), unpushed
8896
9171
  (commits no remote-tracking ref or other local branch reaches), or has
8897
9172
  initialized submodules. Without --worktrees, the report leaves out the
@@ -8922,14 +9197,17 @@ SWEEPING FINISHED WORKTREES
8922
9197
  an unexpired device lease. When the simulator or emulator list cannot be
8923
9198
  read, the worktree counts as in use. A device left booted keeps the
8924
9199
  worktree until \`stim stop\` in it, or \`gc --idle <duration>\`, shuts
8925
- the device down.
9200
+ the device down. A worktree that holds the running stim or the realpath of
9201
+ the \`stim\` on PATH is in use too, and \`stim worktree remove\` refuses it
9202
+ even with --force, as it refuses an installed stim-server service's checkout.
8926
9203
 
8927
9204
  RECENT ACTIVITY keeps a worktree gc would otherwise remove until
8928
9205
  gc.worktreeGraceMinutes (120 by default, \`guide settings\`) have passed
8929
9206
  since the latest of: a write to its git index, HEAD or HEAD reflog, a write
8930
9207
  to its Stim workspace state or log files, and, for a merged branch, the
8931
9208
  committer date of the commit that brought it into the default branch, or
8932
- the time its pull request was merged or closed. That
9209
+ the time its pull request was merged or closed, and, for a dirty finished
9210
+ worktree, the newest change to its uncommitted or untracked files. That
8933
9211
  window is when an agent that just merged runs \`stim stop\` and \`stim
8934
9212
  worktree remove\` itself. The reason is recent-activity, and the report
8935
9213
  and the JSON eligibleAt field say when it becomes removable. When that
@@ -8984,20 +9262,56 @@ SWEEPING FINISHED WORKTREES
8984
9262
 
8985
9263
  NO COMMITS OF ITS OWN: a worktree whose HEAD the default branch contains
8986
9264
  and whose branch has no commit made on it (fresh, cut from another
8987
- branch, or reset onto one; the MERGED rule above) holds nothing git would
8988
- lose, but looks the same as one just made by git worktree add. Plain gc keeps
8989
- it (reason not-merged, "no commits of its own beyond origin/<default>").
8990
- \`--worktrees\` removes it when idle: by its recorded use when there is
8991
- one, else by the RECENT ACTIVITY sources above being older than
8992
- --older-than days (reason recently-used while they are newer, and
8993
- last-use-unknown when they cannot be read). Automatic maintenance removes it
8994
- the same way in its daily age sweep, after maintenance.olderThanDays, only
8995
- with maintenance.removeIdleWorktrees (off by default), and at disk pressure
8996
- level 3 once that activity is older than
8997
- maintenance.pressureProtectMinutes and at least 5 minutes old, whatever
8998
- the setting; unreadable activity keeps it. Removal re-checks, under the removal
8999
- locks, that HEAD did not move and the worktree is still idle, and
9000
- \`stim worktree remove\` still refuses dirty or unpushed work.
9265
+ branch, reset onto one, or a detached HEAD; the MERGED rule above) holds
9266
+ nothing git would lose, but looks the same as one just made by git
9267
+ worktree add. gc --delete and the maintenance worktree check remove a clean
9268
+ one without an open pull request once the RECENT ACTIVITY sources above
9269
+ are older than gc.worktreeGraceMinutes and maintenance.protectRecentHours
9270
+ (2 hours by default), and never younger than 5 minutes (reason
9271
+ recent-activity until then, activity-unknown when they cannot be read). At
9272
+ disk pressure level 3 the window is maintenance.pressureProtectMinutes,
9273
+ still at least 5 minutes. Removal re-checks, under the removal locks, that
9274
+ HEAD did not move and the worktree is still idle, and \`stim worktree
9275
+ remove\` still refuses dirty or unpushed work. A process Stim does not
9276
+ track, such as a benchmark run from a detached build, does not keep it.
9277
+
9278
+ ARCHIVED CHANGES: a dirty worktree that is otherwise finished, idle past
9279
+ RECENT ACTIVITY and not in use is archived, then removed, by gc --delete
9280
+ and the maintenance worktree check. Under the removal locks Stim writes
9281
+ \`git diff --binary HEAD\` (staged and unstaged changes together) to
9282
+ tracked.patch, the untracked files git does not ignore to untracked.tar,
9283
+ HEAD's commits the default branch lacks to head.bundle,
9284
+ with a manifest.json naming the branch, HEAD, pull request and files, in
9285
+ $STIM_HOME/archive/worktrees/<repository>/<branch-or-dir>-<timestamp>/.
9286
+ Ignored files (node_modules, build output) are not saved. It verifies the
9287
+ patch with \`git apply --check -R\`, the tarball's file list, and that no
9288
+ archived file changed, again just before git removes the worktree; any
9289
+ failure keeps the worktree and deletes the archive. It also keeps a
9290
+ worktree whose archive.enabled is false, or false for a project registered
9291
+ inside it, with archive.maxAgeDays or
9292
+ archive.maxTotalGb 0, whose untracked files or patch exceed
9293
+ archive.maxTotalGb, whose archive would leave less free disk than
9294
+ budget.hardFloorDiskGb, whose staged version of a file differs from its
9295
+ working tree, or that holds an untracked nested repository; disk
9296
+ pressure level 3 never archives. Archives are 0700 and 0600 with no
9297
+ redaction, so an untracked secret such as a .env file that git does not
9298
+ ignore is saved; use archive.enabled false for a sensitive repository. A
9299
+ \`git status\` or editor refresh that rewrites the index restarts RECENT
9300
+ ACTIVITY. The removal line and the gc
9301
+ and maintenance logs name the archive and the restore commands; head.bundle
9302
+ exists only when the default branch lacks HEAD, and lets the restore work
9303
+ after the branch is deleted:
9304
+ git -C <repository> bundle unbundle <archive>/head.bundle (with a bundle)
9305
+ git -C <repository> worktree add --detach <path> <HEAD>
9306
+ git -C <path> apply <archive>/tracked.patch
9307
+ tar -xf <archive>/untracked.tar -C <path>
9308
+ Archive retention, which runs before and after each new worktree archive
9309
+ and after each removal that archives a workspace, deletes worktree archives
9310
+ older than archive.maxAgeDays. A new archive that would not fit beside the
9311
+ others in archive.maxTotalGb keeps its worktree instead, so the oldest are
9312
+ deleted for size only after that cap is lowered. An archive whose removal
9313
+ is still running holds an ownership claim and is never deleted. Plain \`stim worktree remove\` never archives; it
9314
+ refuses dirty work as before.
9001
9315
 
9002
9316
  PULL REQUESTS: with fresh state, gc runs one \`gh api graphql\` query per
9003
9317
  repository that asks for the 20 newest pull requests of each linked worktree's branch, as
@@ -9389,9 +9703,10 @@ THE ONE CASE GC WILL NOT REAP
9389
9703
  summary: "disk usage, workspace build outputs, logs and device recordings, AVD and build-log sizes, the data partition, trimming the shared caches",
9390
9704
  body: () => `DISK
9391
9705
  Logs, state, pidfiles and Xcode DerivedData are under the global workspace
9392
- directory, and \`worktree remove\` reclaims them. \`gc --delete\` clears the
9393
- build outputs of workspaces nobody is using (WORKSPACE BUILD OUTPUTS
9394
- below) and keeps the rest. Gradle retains its normal
9706
+ directory, and \`worktree remove\` reclaims them. \`gc --delete\` lightens
9707
+ every workspace that is not building and did not run a native command in
9708
+ the last 15 minutes, clearing its build outputs (WORKSPACE BUILD OUTPUTS
9709
+ below), and keeps the rest. Gradle retains its normal
9395
9710
  project build directories while sharing task outputs through its build cache.
9396
9711
 
9397
9712
  Android AVDs normally live under ~/.android/avd, and a booted owned AVD can
@@ -9442,18 +9757,40 @@ a changed value.
9442
9757
  WORKSPACE BUILD OUTPUTS
9443
9758
  Each workspace directory holds derived-data/, gradle-build/, android-cas/,
9444
9759
  cache-provider/, macos/build, the staged macos/<Product>.app and macos/staging-*
9445
- leftovers. \`gc\` reports them as one detected cache, "Workspace build
9446
- outputs", with a per-workspace size, last use and verdict. Plain
9447
- \`gc --delete\` clears them for every workspace not in use (see \`guide
9448
- cleanup gc\`), before anything else. \`--older-than <days>\` limits that to
9760
+ leftovers. The project's own android/app/build, android/build,
9761
+ android/.gradle and .build count with them when git ignores every file in
9762
+ the directory and tracks none; one that fails that check, or is not in a
9763
+ git checkout, stays, and \`gc --delete\` prints why when the check fails
9764
+ between the report and the deletion. ios/build stays: React Native's pod
9765
+ install writes podspec facades and codegen output there that only pod
9766
+ install recreates. \`gc\` reports them as one
9767
+ detected cache, "Workspace build outputs", with a per-workspace size, last
9768
+ use and verdict. Plain \`gc --delete\` lightens every workspace, before
9769
+ anything else, by clearing them, and prints the bytes freed per workspace.
9770
+ A workspace whose worktree is locked with git worktree lock is never
9771
+ lightened: not its outputs, not its project's build directories, at any
9772
+ disk pressure level, including gc --older-than and the budget, and gc and
9773
+ the maintenance log name the lock. The outputs also stay when git lists
9774
+ nothing for a project inside a git checkout (code lock-unknown). A
9775
+ running dev server, a booted or hosted simulator or emulator and a managed
9776
+ tunnel no longer keep them. They stay while the workspace is building (its
9777
+ native-run.lock is held, a live or unresolvable build lock or slot names
9778
+ it, or its build claim is live), while its macOS app or build runs or cannot
9779
+ be verified, and for 15 minutes after its last ios, android or macos run,
9780
+ cache hits included, as recorded in state.json's build records. Dev server
9781
+ starts, reloads and logs do not count as a native run. The shared build
9782
+ cache, the Xcode compilation cache, the Gradle caches and the Metro cache
9783
+ are never touched. \`--older-than <days>\` limits that to
9449
9784
  workspaces idle at least that long, and keeps one whose last use is
9450
9785
  unknown. \`--cache workspaces\` acts on them alone, except in the workspace
9451
9786
  directories that plain \`gc --delete\` removes whole (dead projects and
9452
9787
  orphaned directories); \`--cache all\` includes them. macos/ itself,
9453
9788
  macos/runtime.lock and its claim set, workspace.json, state.json, logs/,
9454
- other macOS files, locks and device records stay, as does the project's
9455
- own .build. The workspace keeps its devices and ports. A running, building,
9456
- unverified or hosted macOS app keeps its workspace untouched.
9789
+ other macOS files, locks and device records stay. The workspace keeps its
9790
+ devices, ports and running dev server. Automatic maintenance at disk
9791
+ pressure level 2 clears them by the same rule (DISK PRESSURE LEVELS); level
9792
+ 1 keeps its own rules. A running, building, unverified or hosted macOS app
9793
+ keeps its workspace untouched.
9457
9794
  stim gc --delete --cache workspaces --older-than 7
9458
9795
  Last use is the newest of the lastUsedAt that start, ios, android, reload
9459
9796
  and worktree warm record in state.json, lastBuild.startedAt,
@@ -9470,6 +9807,31 @@ WORKSPACE BUILD OUTPUTS
9470
9807
  build recompiles Swift. The next \`stim macos\` rebuilds its scratch from
9471
9808
  nothing (a full Swift build) and restages the app.
9472
9809
 
9810
+ WORKTREE DEPENDENCIES
9811
+ Plain \`gc --delete\` also lightens linked worktrees: it deletes the
9812
+ node_modules and ios/Pods at the root or a project root of a linked
9813
+ worktree that has a Stim workspace, never the source checkout. A worktree
9814
+ locked with git worktree lock keeps them always, so a rollback checkout
9815
+ stays runnable. Otherwise a worktree keeps them while it is in use (its
9816
+ dev server runs, a native run or build holds it), while it was used in
9817
+ the last 15 minutes (a git index, HEAD or reflog write, a Stim state or
9818
+ log write, or a write at the top of the directory), while git tracks a file in them, while a stim-server service
9819
+ or the stim on PATH runs from it, while a live process has its command
9820
+ line or working directory in it, and when the running processes cannot be
9821
+ listed (always on Windows). It also keeps them when \`stim worktree warm\`
9822
+ could not put the same thing back: warm would not copy that directory from
9823
+ the source checkout (it is missing there, excluded by .worktreeexclude or
9824
+ worktree.exclude, or inside a larger ignored directory such as a wholly
9825
+ ignored ios/), the worktree's lockfile (or ios/Podfile.lock for Pods)
9826
+ differs from the source checkout's, or the source checkout's own copy was
9827
+ not installed from its lockfile. gc records what it deleted in the
9828
+ workspace's lightened.json; the next \`stim start\`, \`stim ios\` or
9829
+ \`stim android\` there runs \`stim worktree warm\` first to restore it, and
9830
+ refuses with STIM_DEPS_FAILED, keeping the record, when those checks no
9831
+ longer pass, when warm fails, or when it leaves a directory missing. A
9832
+ deletion that stopped part-way is finished before the warm. Other commands, such as a test run, find the directory missing
9833
+ until then. .pnpm-store stays: it is all clones and frees nothing.
9834
+
9473
9835
  WORKSPACE LOGS
9474
9836
  \`gc\` reports the size of each workspace's logs/ (the workspaceLogs
9475
9837
  section of \`gc --json\`). metro.ndjson, client.ndjson and device.ndjson and
@@ -9529,6 +9891,12 @@ SHARED BUILD CACHES
9529
9891
  report-only because every Gradle build shares it. Stim reports its size
9530
9892
  but never prunes or empties it, including with --older-than or --cache all.
9531
9893
 
9894
+ A cache directory that \`gc --delete --older-than\` cannot list (EACCES,
9895
+ EPERM or ELOOP, for example) is a failed trim, not an empty one: the run
9896
+ prints "N entries in <dir> could not be read or removed", records the cache
9897
+ as failed and exits 1. Fix the directory's permissions and run it again. A
9898
+ directory that is missing or is not a directory is not a failure.
9899
+
9532
9900
  Trim rather than empty. Emptying costs the next build in every project the
9533
9901
  time the cache was saving.`
9534
9902
  }
@@ -9649,12 +10017,16 @@ KEYS STIM READS
9649
10017
  embedded JS, no Metro, cache keyed -release-sim, and
9650
10018
  a JS-bundle swap on cache hits. The \`--configuration\`
9651
10019
  flag overrides this per invocation. Unset means Debug.
9652
- ios.remote "proxy", "eas", or a named approved Mac from
9653
- remote.machines, with the same meaning as --remote.
9654
- "auto" places on an approved Mac when this machine is full or
9655
- busy. Unset runs here; "local" runs here even when a
9656
- lower layer says otherwise. A --device run ignores
9657
- it. See lifecycle hosted-ios.
10020
+ ios.remote where the simulator runs, with the same meaning as
10021
+ --remote: "local" here, even when a lower layer says
10022
+ otherwise; "auto" here, and on an approved Mac from
10023
+ remote.machines when this machine is full or busy;
10024
+ "remote" on the first approved Mac in remote.machines
10025
+ order with room, here only when none takes it;
10026
+ "remote-only" the same but never here; a named
10027
+ approved Mac exactly that Mac; "proxy" or "eas" a
10028
+ remote device backend. Unset runs here. A --device run
10029
+ ignores it. See lifecycle hosted-ios.
9658
10030
  ios.projectPath directory under the app holding a bare React Native
9659
10031
  app's Xcode project and Podfile; default "ios". "."
9660
10032
  is the app directory itself (RNTester's layout).
@@ -9875,14 +10247,13 @@ ${ANDROID_AVD_CONFIG_HELP.map((line) => ` ${line}`).joi
9875
10247
  password, handed to apksigner through its
9876
10248
  environment, never its command line. Unset means
9877
10249
  the debug keystore's fixed "android".
9878
- android.remote "proxy", "eas", or a named approved Mac in
9879
- remote.machines; "auto" places on an approved Mac when
9880
- this machine is full or busy. Unset runs here; "local"
9881
- runs here even when a lower layer says otherwise.
9882
- A --device run ignores it. See lifecycle
9883
- hosted-android.
9884
- remote.easFallback true lets "auto" (ios.remote, android.remote or
9885
- --remote auto) run the simulator or emulator on a
10250
+ android.remote where the emulator runs, with the same words as
10251
+ ios.remote: "local", "auto", "remote", "remote-only",
10252
+ a named approved Mac in remote.machines, "proxy" or
10253
+ "eas". Unset runs here. A --device run ignores it. See
10254
+ lifecycle hosted-android.
10255
+ remote.easFallback true lets "auto" or "remote" (ios.remote, android.remote
10256
+ or --remote) run the simulator or emulator on a
9886
10257
  billed EAS Simulator when this machine is at its
9887
10258
  concurrency.maxDevices cap (or has runs queued) and no
9888
10259
  remote.machines entry takes the run, instead of waiting
@@ -10016,6 +10387,16 @@ ${ANDROID_AVD_CONFIG_HELP.map((line) => ` ${line}`).joi
10016
10387
  compiled into Contents/Resources before signing
10017
10388
  macos.resources map of Contents/Resources destinations to source paths
10018
10389
  relative to Package.swift; see stim guide macos
10390
+ macos.remote where stim macos launches the app, with the words of
10391
+ ios.remote except "eas" and "proxy": "local" here;
10392
+ "auto" here unless this machine is busy, then an
10393
+ approved Mac in remote.machines; "remote" on the
10394
+ first approved Mac in order with room, here only when
10395
+ none takes it; "remote-only" the same but never here,
10396
+ which keeps the app from taking focus on this Mac; a
10397
+ named Mac exactly that Mac. The --remote flag
10398
+ overrides it. A recorded hosted app keeps its Mac
10399
+ until stim stop. Unset runs here. See stim guide macos.
10019
10400
  web.url the page \`stim web\` opens in the owned Chrome, an
10020
10401
  http:// or https:// URL. {port:<label>} becomes the
10021
10402
  workspace's named port (allocated like \`stim ports
@@ -10073,8 +10454,9 @@ ${ANDROID_AVD_CONFIG_HELP.map((line) => ` ${line}`).joi
10073
10454
  them from the environment or the machine layers.
10074
10455
 
10075
10456
  Each setting takes its documented type: string, array of strings, number,
10076
- boolean, or object. ios.remote and android.remote accept backend names or
10077
- tailnet machine names. metro.tunnel, web.viewport,
10457
+ boolean, or object. ios.remote, android.remote, macos.remote and remote.build
10458
+ accept placement words (local, auto, remote, remote-only) or tailnet machine
10459
+ names, and ios.remote and android.remote also the backend names. metro.tunnel, web.viewport,
10078
10460
  optimizations.android.compilerCache and optimizations.android.pch take only
10079
10461
  their listed choices. A value of the wrong
10080
10462
  type or outside those choices is refused by name on every command that resolves
@@ -10300,6 +10682,21 @@ simulator sessions on, by MagicDNS name with an optional serve port (default
10300
10682
  stim settings set remote.machines '["janics-mac-mini"]'
10301
10683
  stim doctor --fix
10302
10684
 
10685
+ The order of \`remote.machines\` is the priority order for every workload: the
10686
+ build, simulator, emulator and macOS app each go to the first listed machine that
10687
+ has approval, a free slot and a 5-minute load per core under that machine's own
10688
+ \`server.maxLoadPerCore\`, and the next one in order only when it does not. A
10689
+ machine at its caps, over its load limit, unreachable or lacking approval is
10690
+ skipped; load does not rank the machines that qualify, so the first one fills
10691
+ before the next is used. Put the fastest machine first and set the order with
10692
+ one command; \`stim doctor\` prints it as \`placement order\` and gives each
10693
+ machine a \`priority\` in its JSON:
10694
+
10695
+ stim settings set remote.machines '["fast-mini","slow-mini"]'
10696
+
10697
+ A host reports its own load limit in its device offer; a stim-server older than
10698
+ that does not, and this machine's \`server.maxLoadPerCore\` stands in for it.
10699
+
10303
10700
  Automatic membership is separate from approval. In Desktop Settings > Remote
10304
10701
  Machines, use Builds enabled and Simulators enabled for this machine or a
10305
10702
  configured remote. The equivalent machine settings list the excluded members;
@@ -10387,9 +10784,9 @@ An invalid remote.machines setting reports an error and preserves every saved cr
10387
10784
  replaced machine, run it, then re-add the name to request a new approval.
10388
10785
  A definite revoked or lapsed request can be requested again with --fix.
10389
10786
  An in-progress approval inspection reports busy rather than replacing its
10390
- pending token. \`stim macos --remote <machine>\` runs a macOS app on an
10391
- approved remote Mac (stim guide macos); \`ios\` and \`android\` do not yet
10392
- place sessions on these Macs. To view or control a hosted macOS app, a person
10787
+ pending token. \`stim macos --remote <placement>\` or \`macos.remote\` runs a macOS
10788
+ app on an approved remote Mac (stim guide macos), and \`ios.remote\` and
10789
+ \`android.remote\` place simulators and emulators on them. To view or control a hosted macOS app, a person
10393
10790
  on that Mac approves Screen & System Audio Recording and Device Control and Data
10394
10791
  Access (Accessibility on macOS 26 and earlier) for Stim Host, the app
10395
10792
  \`stim-server service install\` runs the server under.
@@ -10436,18 +10833,35 @@ builds, or the build messages change; their Stim builds can differ. A
10436
10833
  stim-server too old to report one needs the same Stim build as this machine.
10437
10834
 
10438
10835
  \`remote.build\` (machine scope, default auto) selects placement for
10439
- \`ios\`, \`android\` and \`macos\`. \`--remote-build <auto|local|name>\`
10836
+ \`ios\`, \`android\` and \`macos\`. \`--remote-build <local|auto|remote|remote-only|name>\`
10440
10837
  overrides STIM_REMOTE_BUILD, which overrides the setting. A blank environment
10441
- value is unset. Trimmed auto/local are case-insensitive. Machine names match
10838
+ value is unset. \`doctor\` flags an invalid value in the machine config or in
10839
+ STIM_REMOTE_BUILD before a build refuses on it. Trimmed placement words are case-insensitive. Machine names match
10442
10840
  configured names case-insensitively, with port 7443 when omitted; reports use
10443
10841
  the configured entry.
10444
10842
 
10445
- auto follows remote.buildMode, considering only enabled automatic pool members
10446
- local builds only on this machine for this invocation
10447
- name requires a matching entry in remote.machines, already paired and
10448
- approved for builds; ignores remote.buildMode and this machine's load/slot gating
10843
+ local builds only on this machine for this invocation
10844
+ auto (default) here while this machine has capacity, a remote
10845
+ machine from remote.machines when it does not; only enabled
10846
+ automatic pool members count
10847
+ remote on the first remote machine in remote.machines order that
10848
+ accepts the build, here only when none does
10849
+ remote-only the same, never here: when no remote machine accepts the build
10850
+ it refuses with STIM_OFFLOAD_REFUSED
10851
+ name requires a matching entry in remote.machines, already paired and
10852
+ approved for builds; ignores this machine's load/slot gating
10853
+
10854
+ \`remote.buildMode\` and STIM_REMOTE_BUILD_MODE were removed and are no longer read.
10855
+ \`doctor\` and \`stim settings\` report any left in a config file or the environment,
10856
+ \`stim settings set remote.buildMode\` refuses with the \`remote.build\` command that
10857
+ replaces it (auto stays auto, force becomes remote, off becomes local), and
10858
+ \`stim settings unset remote.buildMode\` removes the leftover key from every layer
10859
+ that has it (--scope limits it to one).
10449
10860
 
10450
10861
  A name never prompts for pairing, tries another remote machine or falls back here.
10862
+ remote-only refuses instead of building here for a build that cannot leave this
10863
+ machine (device, Release, an unknown simulator runtime, the build cache off), and
10864
+ when no pairing or approval exists.
10451
10865
  Any refusal or failure is STIM_OFFLOAD_REFUSED with the remote machine and reason.
10452
10866
  Device, Release/non-Debug, --remote, Android CAS compiler builds, builds with
10453
10867
  the build cache off, and an unknown simulator runtime cannot build there and
@@ -10474,26 +10888,29 @@ Copyable agent prompt:
10474
10888
  Run stim ios --remote-build janics-mac-mini. If it refuses, report the
10475
10889
  STIM_OFFLOAD_REFUSED reason and remedy; do not retry with another placement.
10476
10890
 
10477
- With remote.build auto, \`remote.buildMode\` decides where an iOS simulator Debug build or an Android
10478
- emulator debug build or a stim macos SwiftPM Debug build compiles:
10891
+ Which remote machine takes a build that leaves this machine follows the order of
10892
+ \`remote.machines\`: the first machine that accepts it, meaning it has a free slot
10893
+ (\`concurrency.maxBuilds\`), enough disk, a matching toolchain, and a load per core
10894
+ under its own \`server.maxLoadPerCore\`. A machine past its limit is skipped like a
10895
+ full one and the next in order is tried; the first machine fills to its caps before
10896
+ the next one gets a build. A machine that already holds the exact completed
10897
+ artifact goes ahead of the order, since that needs no build. With \`auto\` the
10898
+ build leaves this machine only when it is busy:
10479
10899
 
10480
10900
  auto (default) here while this machine has capacity: a free
10481
10901
  \`concurrency.maxBuilds\` slot (always, with no build limit) and a
10482
- load per core under \`server.maxLoadPerCore\`. Otherwise on a
10483
- remote machine that accepts the build and is expected to be faster:
10484
- while every slot here is busy, any remote machine that accepts; while only
10485
- the load is high, one whose load per core is lower than this
10486
- machine's. A machine too old to report its load counts only while every
10487
- slot here is busy.
10488
- force on a remote machine whenever one accepts it
10489
- off here when local remains enabled in the automatic build pool
10490
-
10491
- For React Native and Expo, among machines that can take a new build, a matching dependency installation
10492
- ranks before load. The worker checks the requested project, lockfile, install
10493
- arguments and the major of the Node the app pins, and verifies that its modules
10494
- and install marker remain present.
10495
- Older workers' dependency hints and mere checkout or build-folder presence do
10496
- not influence this ranking. Exact completed-artifact hits still come first.
10902
+ load per core under \`server.maxLoadPerCore\`. Otherwise on the first
10903
+ remote machine in order that accepts the build. A machine too old to
10904
+ report its load counts only while every slot here is busy.
10905
+ remote on the first remote machine in order that accepts the build, whatever
10906
+ load this machine is under; here only when none does
10907
+ remote-only as remote, but when none does the build refuses
10908
+
10909
+ A worker that holds a matching dependency installation does not rank ahead of an
10910
+ earlier machine in the order; it only saves the install on the machine that gets
10911
+ the build. The worker checks the requested project, lockfile, install arguments
10912
+ and the major of the Node the app pins, and verifies that its modules and install
10913
+ marker remain present.
10497
10914
 
10498
10915
  The worker installs dependencies with the lockfile nearest the app: the first
10499
10916
  pnpm-lock.yaml, yarn.lock or package-lock.json in its directory or above it in
@@ -10518,10 +10935,12 @@ Load per core is the 5-minute load average divided by the CPU count; a machine's
10518
10935
  native builds are its Stim runs in prebuild, pods or compile on that machine, not
10519
10936
  the ones it offloaded.
10520
10937
  \`server.maxLoadPerCore\` (default 2) is also the busy threshold for
10521
- automatic iOS and Android device placement. It is the load per core at which a machine
10522
- counts as saturated, both here and on a remote machine.
10938
+ automatic iOS, Android and macOS placement. It is the load per core at which a
10939
+ machine counts as saturated: each machine applies its own value, and a remote
10940
+ machine past it is skipped like a full one. \`stim status\` shows this machine's
10941
+ load per core against it, as \`capacity.load\` in JSON.
10523
10942
 
10524
- STIM_REMOTE_BUILD_MODE overrides it for one command. Device, Release and
10943
+ Device, Release and
10525
10944
  \`--remote eas|proxy\` builds, Android builds with the Apple Clang CAS compiler cache,
10526
10945
  and iOS/Android runs with the build cache off, always build here. Hosted iOS
10527
10946
  Debug builds can use a separate --remote-build, targeting the hosting Mac's
@@ -10534,7 +10953,7 @@ verified compile; remote output is not published under an unverified client key.
10534
10953
  An offloaded iOS/Android build first runs prebuild (and \`pod install\` for iOS) here,
10535
10954
  then asks every paired machine what it can build. It takes one whose Stim
10536
10955
  build matches this Mac and, for iOS and macOS, CPU matches the target simulator, with enough disk, that does not decline,
10537
- preferring the one that already holds this repository, then the least loaded,
10956
+ taking the first in remote.machines order that qualifies,
10538
10957
  and moves to the next one in that order when a machine that offered fails the
10539
10958
  sync or refuses to start the build.
10540
10959
  After a local and configured-provider miss, native Xcode simulator builds can
@@ -10566,7 +10985,7 @@ xcodebuild -downloadPlatform iOS -buildVersion <version>, or a pointer to the
10566
10985
  Xcode problem when xcode-select is the cause. A stim-server that reports no
10567
10986
  developerDir keeps the prose fix. It also needs an
10568
10987
  iPhone simulator on the target runtime. A missing or different global CocoaPods
10569
- keeps automatic placement from choosing the machine for a project that does not
10988
+ keeps auto, remote and remote-only placement from choosing the machine for a project that does not
10570
10989
  pin it, unless the machine already holds the exact artifact; the placement line
10571
10990
  says why and doctor shows the install command. A named --remote-build still
10572
10991
  sends the build, and a pod install that fails there reports "pod install failed"
@@ -10730,6 +11149,9 @@ be setup steps are supplied by Stim on the command lines it composes itself:
10730
11149
  SWIFT_OTHER_PREFIX_MAPPINGS on a Swift 6.4+ toolchain -- so no
10731
11150
  Podfile post_install block (Xcode 26+ only, and skipped when the
10732
11151
  project configured ccache, which defeats it)
11152
+ Debug builds for the iOS simulator also get
11153
+ DEBUG_INFORMATION_FORMAT=dwarf, which skips the dSYM bundle;
11154
+ Release and device builds keep the project's setting
10733
11155
  gradlew --build-cache -- so no org.gradle.caching=true in a committed
10734
11156
  gradle.properties. Debug builds add
10735
11157
  -PreactNativeArchitectures=<target ABI> when the owned
@@ -11188,7 +11610,7 @@ var macos_default = {
11188
11610
 
11189
11611
  If Stim is not installed globally, replace stim with npx stim.
11190
11612
 
11191
- ios --remote <machine> shares remote.machines and its separate device-host
11613
+ ios --remote <placement> shares remote.machines and its separate device-host
11192
11614
  approval. Read guide lifecycle hosted-ios for simulator placement and Metro.
11193
11615
 
11194
11616
  Run from the directory containing Package.swift. This prototype builds one
@@ -11248,7 +11670,10 @@ owned app, including copies opened through LaunchServices (open, agent-device
11248
11670
  open). stim macos, stim stop, stim worktree remove and stim gc --delete stop all
11249
11671
  of them: SIGTERM, up to 5 s, then SIGKILL, and success only once each has
11250
11672
  exited. When an app cannot be verified or does not exit, stop reports
11251
- STIM_MACOS_OWNER_UNVERIFIED or a failure, and removal keeps the workspace.
11673
+ STIM_MACOS_OWNER_UNVERIFIED or a failure, and removal keeps the workspace. For
11674
+ a hosted session the same rule covers every app bundle the session received, so
11675
+ stop and a replacement launch also end a copy that LaunchServices started from
11676
+ an earlier attempt.
11252
11677
  Local stim macos starts the app in the background without activating it or
11253
11678
  changing focus: it sets STIM_BACKGROUND_LAUNCH=1 in the app's environment, which
11254
11679
  Stim Desktop honors, including for reopen events from open -g. An app that
@@ -11292,7 +11717,7 @@ Apple Swift, xcrun and xcodebuild selections can be reproduced; custom launchers
11292
11717
  require the checkout and otherwise miss safely. SWIFT_EXEC and SWIFT_EXEC_MANIFEST
11293
11718
  compiler overrides are resolved and versioned separately and require the checkout.
11294
11719
  The SDK selected by SDKROOT or SWIFTPM_SDKROOT_macosx is checked by real path and build version. Explicit local placement,
11295
- remote build mode off, disabled pool members and remoteBuildCache
11720
+ disabled pool members and remoteBuildCache
11296
11721
  false skip peer lookup. Older workers keep their normal compilation path.
11297
11722
  --plan validates the Swift Package directory, macos settings, development plist
11298
11723
  and declared resources without building, signing, stopping or launching
@@ -11324,17 +11749,16 @@ line instead of when the app exits.
11324
11749
 
11325
11750
  BUILD OFFLOAD
11326
11751
 
11327
- --remote-build <auto|local|name> overrides STIM_REMOTE_BUILD and the
11752
+ --remote-build <local|auto|remote|remote-only|name> overrides STIM_REMOTE_BUILD and the
11328
11753
  machine setting remote.build (default auto). local builds here; a name
11329
- requires the matching configured and paired remote Mac, ignoring remote.buildMode
11330
- and this Mac's capacity. Any failure is STIM_OFFLOAD_REFUSED without a local xcodebuild, Gradle or
11754
+ 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
11331
11755
  SwiftPM compile or another machine. Invalid, unlisted or unpaired selections
11332
11756
  refuse before stopping the app or changing its build record. See stim guide errors STIM_OFFLOAD_REFUSED.
11333
11757
 
11334
- With remote.build auto, remote.buildMode places these SwiftPM Debug builds:
11335
- auto builds here while this Mac has capacity, force uses an approved build
11336
- machine when one accepts, and off builds here when local remains in the automatic
11337
- build pool. remote.buildPoolDisabled excludes members from automatic placement,
11758
+ remote.build places these SwiftPM Debug builds: auto builds here while this Mac has
11759
+ capacity, remote uses the first approved build machine in remote.machines order that
11760
+ accepts and otherwise builds here, remote-only never builds here and refuses when none
11761
+ accepts, and local builds here. remote.buildPoolDisabled excludes members from automatic placement,
11338
11762
  including its local fallback. Explicit local or named placement bypasses membership.
11339
11763
  Configure remote.machines
11340
11764
  and approve build access as described in stim guide settings. The worker needs
@@ -11356,8 +11780,8 @@ identity comes from the worker's toolchain. Any other combination keeps the matc
11356
11780
  The client validates the development plist and resource entries before asking a machine. It verifies
11357
11781
  the returned archive's sha256, bundle identifier, executable, declared resources
11358
11782
  and ad hoc signature
11359
- before replacing the owned bundle, then launches locally as usual. With auto, every offload
11360
- failure falls back to the local build, including force. Failed staging preserves
11783
+ before replacing the owned bundle, then launches locally as usual. With auto or remote, every offload
11784
+ failure falls back to the local build. Failed staging preserves
11361
11785
  the previous bundle. No failed artifact is promoted or cached. The build record
11362
11786
  is unchanged by invalid, unlisted or unpaired setup refusals. It
11363
11787
  carries buildMachine (selected) and builtOn (here or the machine, absent until
@@ -11519,19 +11943,40 @@ used.
11519
11943
 
11520
11944
  ON ANOTHER MAC
11521
11945
 
11522
- stim macos --remote <machine> builds the Debug app on this Mac and runs it on an
11946
+ stim macos --remote <machine> or macos.remote builds the Debug app on this Mac and runs it on an
11523
11947
  approved remote Mac over the tailnet, without SSH. The Mac must be listed
11524
11948
  in remote.machines and approved: stim doctor --fix makes a setup ticket, a
11525
11949
  person on that Mac runs stim-server setup with that ticket and expiry (guide
11526
11950
  settings), and stim doctor then pins that Mac's stim-server key. Stim goes ahead
11527
11951
  only with the stim-server that proves it holds that key.
11528
- A refusal or an unreachable host fails the command; it never launches here
11952
+ A refusal or an unreachable host fails the command; with a named Mac it never launches here
11529
11953
  instead.
11530
11954
 
11531
- The --remote value is a remote Mac name from remote.machines. macOS refuses
11532
- eas and proxy because it has neither backend. It also refuses auto until
11533
- automatic placement ships. These reserved names are case-insensitive and
11534
- trimmed; they refuse with STIM_BAD_ARG before state access or a connection.
11955
+ The --remote flag and the macos.remote setting take the placement words of
11956
+ ios.remote and a remote Mac name from remote.machines; the flag overrides the
11957
+ setting:
11958
+
11959
+ local launch here (the default when neither is set)
11960
+ auto launch here unless this Mac is busy (host memory pressure or load
11961
+ per core at or above server.maxLoadPerCore), then on the first
11962
+ approved Mac in remote.machines order that has room
11963
+ remote on the first approved Mac in remote.machines order that has room
11964
+ and a load per core under its own server.maxLoadPerCore, here only
11965
+ when none does
11966
+ remote-only the same, never here, so a macOS app never takes focus on this Mac:
11967
+ when every Mac that answered is only full or loaded it waits up to
11968
+ 600 seconds for one with room and then refuses with STIM_AT_CAPACITY;
11969
+ with no Mac that could ever take it (none configured, unreachable,
11970
+ declined for another reason) it refuses at once with
11971
+ STIM_HOSTING_REFUSED and the reason each Mac was skipped
11972
+ <machine> exactly that Mac, with no fallback
11973
+
11974
+ A placement word picks the Mac on a new launch and keeps the recorded one while an
11975
+ app runs there, until stim stop. The command prints a placement: line with the
11976
+ choice and the skipped Macs. macOS refuses eas and proxy because it has neither
11977
+ backend. Reserved names are case-insensitive and trimmed; they refuse with
11978
+ STIM_BAD_ARG before state access or a connection. The device pool switches
11979
+ (remote.devicePoolDisabled) also apply to automatic macOS placement.
11535
11980
 
11536
11981
  Logs: the host records the hosted app's stdout, stderr and exit like a local run.
11537
11982
  stim logs, including --errors, --json and --follow, asks the host over the same
@@ -11618,6 +12063,12 @@ method the relay does not forward, is refused with an agent-device UNAUTHORIZED
11618
12063
  error whose details.reason is STIM_AGENT_REQUEST_REFUSED and whose message names
11619
12064
  the refused command or method; do not retry it.
11620
12065
 
12066
+ longpress, swipe, back and the other gestures are not in that list; the
12067
+ longpress and swipe that stim guide lifecycle lists belong to hosted Android and
12068
+ iOS sessions. click <ref> --button secondary reaches agent-device, but its native
12069
+ macOS backend, which the lease requires, answers UNSUPPORTED_OPERATION, so a
12070
+ hosted macOS app has no right click or context menu yet.
12071
+
11621
12072
  Agents that built, launched and drove test copies on another Mac with an SSH
11622
12073
  script such as mini-desktop.sh use these instead:
11623
12074
 
@@ -11626,7 +12077,8 @@ script such as mini-desktop.sh use these instead:
11626
12077
  launch the same command; the host launches it under the
11627
12078
  session's own home
11628
12079
  shot <out.png> agent-device screenshot --remote-config <path>
11629
- click, rclick, key, type agent-device click, press or type --remote-config <path>
12080
+ click, key, type agent-device click, press or type --remote-config <path>
12081
+ rclick unavailable: secondary click is not supported
11630
12082
  quit stim stop (verified stop; the host frees the slot
11631
12083
  and removes the app's defaults)
11632
12084
  clean stim worktree remove
@@ -11637,6 +12089,15 @@ The host's own owned devices count toward concurrency.maxDevices.
11637
12089
 
11638
12090
  The agent-device rows work once the host's agent field names agent-device.
11639
12091
 
12092
+ On macOS 27, agent-device screenshot of a hosted app can return an image of the
12093
+ window-sharing indicator (66x20 points, 132x40 pixels) instead of the app
12094
+ window. macOS adds that small window to the app while something captures the
12095
+ app, and agent-device picks it as the front window (callstack/agent-device#3961).
12096
+ snapshot -i lists it as an axdialog named "Window" with a
12097
+ WindowSharingSessionButton before the real window. Read the UI with snapshot
12098
+ until agent-device selects the app's main window. Commands that use the front
12099
+ window's frame, such as scroll, may also target the indicator.
12100
+
11640
12101
  DESKTOP DOGFOOD
11641
12102
 
11642
12103
  This repository's apps/desktop/.stim.json selects the full StimDesktop app,