Changelog
Full release history is also on GitHub Releases.
1.27.0
Stim Desktop and the Stim phone app ship through their own workflows; this release covers the twelve npm packages (@stim-cli/core, @stim-cli/cache, @stim-cli/cache-s3, @stim-cli/agent-driver, @stim-cli/agent-driver-agent-device, @stim-cli/metro, @stim-cli/expo-build-cache, stim, @stim-cli/ci, @stim-cli/transport, @stim-cli/transport-tailscale and @stim-cli/server). No package publishes for the first time in this release. Commits that only changed those apps, tests or CI are not listed: #4341, #4346, #4376, #4423, #4424, #4425, #4426, #4427, #4428 and #4439, and the Stim Desktop parts of #4331 and #4332.
New
gc --deletelightens workspaces that did not run a native build in the last 15 minutes. A workspace's build outputs (derived-data/and the rest under its Stim workspace, and the project's ownandroid/app/build,android/build,android/.gradleand.buildwhen git ignores every file in them) are now cleared once nostim ios,androidormacosrun, cache hits included, has happened there for 15 minutes. The time comes from the workspace's build records, not from its logs, so a dev server that runs all day no longer keeps the outputs; only a native run, a build lock or slot, an active build claim or a running macOS app does.ios/buildis never cleared, becausepod installwrites codegen output and podspec facades there that onlypod installrecreates. Maintenance at disk pressure level 2 now clears build outputs and build directories by the same rule instead of its 10-minutemaintenance.pressureProtectMinuteswindow and full in-use check, and no longer deletesios/build. The shared build cache, the Xcode compilation cache and the Gradle and Metro caches are not touched. The report sayswould be LIGHTENEDand lists the in-tree directories;gc --jsonaddsinTreeDirsto eachworkspaceBuildOutputsentry. Fixes #4397 and #4413. (stim,@stim-cli/core) 5d3c12793, d850b937bgc --deletelightens the dependencies of idle linked worktrees, and the next run restores them.node_modulesandios/Podsof a linked worktree that has a Stim workspace are deleted when the worktree has had no activity for 15 minutes andstim worktree warmcan put the same versions back: the source checkout has the directory, its installed tree matches its lockfile, the worktree's lockfiles (pnpm-lock.yaml,package-lock.json,yarn.lock,bun.lock,bun.lockb, orios/Podfile.lockfor Pods) equal the source checkout's, and the directory is not excluded from warm. The source checkout, a running dev server,maintenance.keeppins and the worktree gc runs in are kept, and so is a worktree that the installed stim-server service, thestimon PATH, or any live process's command line or working directory points into, or every worktree when the process list cannot be read. Before deleting, gc writesworkspaces/<id>/lightened.json; the nextstim start,iosorandroidin that workspace runsstim worktree warmfirst, and refuses withSTIM_DEPS_FAILEDwhen the restore check or warm fails. Maintenance at disk pressure level 3 writes the same record when the same check passes. The report gains aWorktree dependenciessection, andgc --jsongainssections.worktreeDependenciesand the result kindworktreeDependencies. Fixes #4414, refs #4397. (stim,@stim-cli/core) 83fa540ee- Finished worktrees with uncommitted files are archived, then removed. A linked worktree that is otherwise removable (merged into the default branch or its pull request merged or closed, pushed, not locked or in use) but has uncommitted or untracked files was kept forever. gc and automatic maintenance now write
tracked.patch,untracked.tar,head.bundle(the HEAD commits the default branch lacks) andmanifest.jsonin a new directory under$STIM_HOME/archive/worktrees/<repo>/once the worktree has been idle forgc.worktreeGraceMinutes(120), verify the archive, check again just beforegit worktree remove --forcethat nothing changed, and then remove the worktree. The removal line, the gc log and the maintenance log name the archive and the commands that restore it. Ignored files are not saved.archive.enabled false, for the worktree or a project registered inside it, keeps such worktrees as before;archive.maxAgeDays(30) andarchive.maxTotalGb(5, a budget of its own for worktree archives) bound them, and an archive that would not fit or would take free disk underbudget.hardFloorDiskGbkeeps the worktree. Disk pressure level 3 andstim worktree removenever archive. Clean worktrees with no commits of their own, detached HEADs included, whose HEAD the default branch contains and which have no open pull request, now count as finished once idle formax(gc.worktreeGraceMinutes, maintenance.protectRecentHours), 2 hours by default, instead of only under disk pressure. A worktree that holds the runningstimor thestimon PATH is in use, andstim worktree removerefuses it even with--force.stim guide agentnow tells agents to remove the worktrees they create and keep scratch files outside them. Fixes #4398 and, on Windows, #4429. (stim,@stim-cli/core) a4cade8ca, e5182d76b STIM_TRACE=<path>writes a Chrome trace of a command run. The file opens in Perfetto andchrome://tracing. It has one span per child process run through the exec wrapper, with its arguments, working directory and outcome; the build phases thatstats.jsonand the build ledger use;worktree warmsteps; the fingerprint, a fingerprint memo hit and the wait for Metro. Nestedstimcommands and the processes a command starts append to the same file. Secret-looking flags and their values,NAME=valueassignments with secret names, URL credentials, bearer tokens,AuthorizationandCookieheaders,curl -uandsecurity -p/-Pcredentials, and common token shapes (JWT,sk_live_/sk_test_,AIza,glpat-,npm_) are redacted; a trace still holds full paths and arguments, so treat it like a log. Unset, each call costs one environment check. Documented instim guide logs(TIMING TRACES) and the website settings page. Fixes #4367, refs #4334. (stim,@stim-cli/core) c18b1cd04, c9929dcb5- A machine-wide build ledger, read with
stim logs --source builds. Everyios,androidandmacosrun, local, offloaded or from a cache, succeeded, failed or cancelled, appends one record to$STIM_HOME/builds/builds.ndjson, which keeps the newest 500. A record has the project, platform, scheme, machine, outcome, failure code, message and remedy, the device name and runtime (never a serial or UDID), and the phase times, including the build machine's.stim logs --source builds [--json]reads it from any directory, like--source gc: alone, and not part of--source all. gc never touches the file. Fields are instim guide logs(BUILD LEDGER). Stim Desktop 0.2.8 lists it. Fixes #4329. (stim,@stim-cli/core) 0eff201e4 - One placement vocabulary for builds, simulators, emulators and macOS apps.
remote.build,ios.remote,android.remoteand the newmacos.remote, and their flags, takelocal,auto(the previous behavior),remote(remote Macs first, this Mac only when none takes it),remote-only(never this Mac) or one machine name;ios.remoteandandroid.remotekeepeasandproxy. The order ofremote.machinesis now the priority for every workload: the first approved, reachable machine with a free slot and a 5-minute load per core under its ownserver.maxLoadPerCoretakes the work, which replaces "least loaded" for devices and "less loaded than this Mac, warmest first" for builds. A machine that already holds the exact completed artifact still goes first. Underremote-only, a device or macOS app waits for a host with room, re-probing every 5 seconds for--waitseconds (600 by default;stim macosalways waits 600), then refuses withSTIM_AT_CAPACITY, and a host that can never take the run refuses at once withSTIM_HOSTING_REFUSED; a build refuses withSTIM_OFFLOAD_REFUSED.stim macos --remoteaccepts the same words, includingauto.stim doctorprints the placement order and apriorityper machine.device-host.offernow reports the host'sresources.maxLoadPerCore.remote.buildModeis removed (see Migration notes).ios --planandandroid --planrefuse an invalidremote.buildorSTIM_REMOTE_BUILDas that setting, and give the refusal a run gives forremote-onlywith no remote machines; doctor flags an invalidremote.buildorSTIM_REMOTE_BUILD. Fixes #4321, #4363, #4364 and #4365. (stim,@stim-cli/core,@stim-cli/server) 65cdbfa8d, d7b0b36b6 - Debug iOS simulator builds skip the dSYM.
stim iospassesDEBUG_INFORMATION_FORMAT=dwarffor a Debug build for the simulator, so Xcode no longer runsdsymutilafter every link (about 6 s per build onapps/mobile) or writes the dSYM bundle. The built app is byte-identical, so no cache key changes. lldb reads the debug information from the object files in the workspace's DerivedData. Release and device builds keep the project's setting. There is no option. Fixes #4433. 28dd0453f - iOS boot records and SpringBoard crash reports per simulator. The workspace
device.ndjsongetsios_bootrecords keyed by udid for each boot phase (boot_requested,bootstatus_done,springboard_started,viewer_opened,simslim_reboot,restart). When SpringBoard is not running after a boot, Stim copies that simulator's recentSpringBoard-*.ipsreports from~/Library/Logs/DiagnosticReportsinto the workspace logs directory (at most 40 kept) and writes aspringboard_crash_reportsrecord. None of these are errors. Documented instim guide logs(IOS BOOT RECORDS). Refs #4359. 232bd0a50 - A setup script for Linux build workers.
scripts/linux-worker/setup.shin the repository sets up an Ubuntu 22.04 or 24.04 x86_64 worker for offloaded Android builds: Node 22.12+ with Corepack, JDK 17, the Android SDK packages, Tailscale,@stim-cli/serverand its service;--emulatoradds KVM and an emulator image. The remote machines page documents the Linux worker. Doctor and the build notes no longer suggest a CocoaPods install for a Linux worker, the ccache note and doctor finding namesudo apt install ccache, and adeps-failed: spawn pnpm|yarn|npm ENOENTrefusal names the missing package manager instead of the approval remedy. Fixes #4383. be4745ea6 - A benchmark harness for Stim releases (repository tooling, not published).
scripts/perf/bench.mjstimesworktree warm,start, first and cachediosandandroidruns andstopfor an npm version or a local build, and can publish the results to theperf-databranch.scripts/perf/compare.mjscompares a machine's latest run with the one before, and reports a change within the repetition spread as noise and marks runs taken above--max-load(80). Described indocs/perf.md. Fixes #4334 and #4387. 04a63de9d, 2d50b91c9
Fixes
- A cached iOS app that dyld cannot bind at launch is removed from the cache. A Debug simulator app stored by 1.26.0 could hold a Debug
React.frameworkand a Releasehermesvm.framework, so dyld killed it on every launch and every cache hit installed it again. After a fatal launch of a cache hit, Stim now reads the run's crash reports; when one is a dyldSymbol missingfailure between two images inside the app's own bundle, it removes that cache entry and fails withSTIM_LAUNCH_FAILED, naming the entry and asking to run again. A missing system library, any other crash and an app built by the same run leave the cache alone. A cache provider's copy cannot be removed; the remedy points at--no-build-cache. Hosted and remote devices are unchanged. Fixes #4412. ef46995f7 pod installno longer drops the React prebuilt build configuration markers. CocoaPods cleans every pod directory when it installs any pod and its glob skips dotfiles, so.last_build_configurationvanished fromReact-Core-prebuilt,hermes-engineandReactNativeDependencieswhile their Release binaries stayed, and React Native 0.88 then linked a Debug build against the Release core. Everypod installStim runs, locally, on a build worker and inworktree warm --refresh, now puts a marker back when its pod directory is the one that existed before, and says so in apodsnote. Fixes #4377. 8ec1967aestim iosrestarts an owned simulator once when SpringBoard is not running. A SpringBoard that started and then died passed every readiness check, andsimctl launchfailed with "The system shell probably crashed". The post-boot check now requires a SpringBoard process, shuts the simulator down through centralized teardown and boots it once more; if SpringBoard is still missing, the boot fails withstim stopthenstim iosas the remedy. Fixes #4074. d9f132f71- The SimSlim skip works after a SimSlim reboot. The SpringBoard pid was read before the rebooted SpringBoard finished starting, so the record was not written and the next
stim iosran SimSlim again (1.5 to 2.9 s). Stim now waits for SpringBoard before reading it. Fixes #4350. 9ea54a9a1 - SwiftUI
Invalid Configurationfaults are warnings. Records with subsystemcom.apple.SwiftUIand categoryInvalid Configuration, such as duplicateForEachids in the Expo dev launcher, counted as app errors at launch. They stay indevice.ndjsonandstim logsat levelwarn. Fixes #4378. a3f9809ba - Ctrl-C during the launch readiness wait or hosted app delivery reports
STIM_CANCELLED. The first Ctrl-C exited with onlylock cancelled by an interrupt, writing no cancelled build ledger row and leaving a stale build record instim status. Both waits now drain through the normal failure path, asstim stopdoes; exit code stays 130, and a second Ctrl-C still exits at once. Fixes #4389 and #4399. e5d1c3f45, a80406a9b - Hosted launches write the launch markers that
logs --errorsand--slotread. A hosted iOS or Android run wrote nolaunch_attemptmarker or outcome record, sostim logs --errorshad no launch window andstim logs --slotdropped the shared Metro and client records. Fixes #4273. 0956a0860 stim stopon a hosted slot copies the final native logs once, and Ctrl-C interrupts it. The copy ran before and after the host stopped the session, so a stop could take about a minute; a cancellation was reported as a device failure. Fixes #4285. d99124675- The refusal for an unreachable remote Mac names the failing layer.
stim ios --remote <mac>printed the raw handshake error. It now prints the diagnosis and remedystim doctorgives, from onetailscale statusread after the connection failed. Fixes #4385. 26204321e - Device slot wait and reclaim lines tell same-named worktrees apart. Workspaces that share a project name carry the first directory that differs, as
mobile (qa-b-p2); a single checkout keeps the plain name. Payloads are unchanged. Fixes #4379. 7588aa64e - A busy agent-device daemon is reported once per device teardown. The line names the daemon, says that sessions naming the device stay open, and gives
agent-device daemon stop --state-dir <dir>. Fixes #4386. 8e8fb8200 - Verifying that an owned AVD stopped retries the process table read. One slow
psunder heavy load madestim stopandworktree removerefuse although the emulator was gone. The read now retries with a longer timeout and still refuses when both attempts fail. Fixes #4384. ca74f0219 worktree warm --refreshkeeps patch-package patches applied. From a monorepo root, the refresh now installs the app warm prepares as well as the root, counts a change underpatches/as a dependency change in npm projects, runs the project's ownpostinstallafternpm ciwhen npm'signore-scriptsis on, and no longer reinstalls on every refresh whennpm lsalready failed right after the last install. A root-cwd refresh also finds the nested app registered in the worktree, and says when it refreshes only the repository root. Fixes #4067 and #4368. 94b3e03ec, cef7f7f3fworktree warmdeletes the copied pnpm store in the background. The clonednode_modules/.pnpm-store(1.8 s to delete in this repository) and the other known copied directories move into the trash for the background delete thatworktree removeuses. A trash entry thatrmcannot empty keeps itstrash.jsonso gc can finish it. Fixes #4349. 476387308- The iOS launch phase does less work. The Metro location write runs alongside the app process probe, and the launch check reads only the bytes appended to the Metro, device and client logs instead of rereading them every 500 ms, with the native crash probe running beside the poll. Fixes #4373. (
stim,@stim-cli/core) 051ddcb0e - Android runs no longer block on
adbreads. The log collector reads the device clock and app pid asynchronously, the launch check samples the clock once per verification instead of before every crash check, and device boot admission and the owned-emulator path list devices asynchronously. 541c793eb stim gcmeasures sizes four at a time. Shared caches, orphaned workspaces, workspace outputs and unlisted worktrees were measured oneduat a time. A cache directory gc cannot read is now reported as one that could not be read or removed. Fixes #4351. 3025d2cca- The budget gate skips a repeated empty cache trim. Below
budget.minFreeDiskGb, everystart,iosandandroidwalked every shared cache and found nothing (11 to 13 s). A trim that removed nothing is recorded in$STIM_HOME/budget-sweep.json, and the next run within 5 minutes skips it when free disk has not fallen by more than 1 GB and the cache set is unchanged. TheSTIM_LOW_DISKsize scans run concurrently, and an unreadable cache directory counts as a failed trim. Fixes #4348. (stim,@stim-cli/core) 9f5ddbb88 gc --delete --jsoncounts failed cache trims and empties infailures. It reported"failures": 0while exiting 1. Fixes #4366. 14e59b052- The iOS JS swap works with macOS 15's
plutil. It prints the missing-key error on stdout, so every app withoutRCTUseAssetCatalogfell back to a full build. Fixes #4358. 40a91d04a - Paths on Windows. Fingerprint input paths and the static fingerprint's root and git top level use the native realpath, so an 8.3 short name or a differently cased path no longer drops gitignored inputs sent to a worker or puts every input outside the repository; a static digest computed through such a path changes once. A macOS worker's Xcode paths are joined as POSIX paths, so a Windows client prints the right
xcode-selectcommand. The dependency lockfile lookup stops at a project path that is not below the root instead of looping at the filesystem root. Fixes #4401, #4437, #4405 and #4415. (stim,@stim-cli/core) 0e49c510a, a8cbb18d4, cb77db2d3, fe5416bb9
Docs
- Run pinned
npx stim@<version>outside a Stim checkout. Inside this repository, npm can resolve the workspace package and run whicheverstimis first on PATH.RELEASE.md,docs/release-recovery.md,docs/perf.mdanddocs/field-test-protocol.mdsay so. Fixes #4388. c8da4721a
Migration notes
-
gc and maintenance clear idle worktrees sooner.
stim gc --deleteand maintenance at disk pressure level 2 clear the build outputs of a workspace with noios,androidormacosrun in the last 15 minutes, even while its dev server runs.gc --deletenow also deletesnode_modulesandios/Podsof a linked worktree idle for 15 minutes when warm can restore them. Disk pressure level 3 deletes them as before, by its own 10-minute window and whether or not warm can restore them, and records them for restore only when it can. After a deletion that wrote a restore record, the nextstim start,iosorandroidthere runsstim worktree warmfirst. Other commands, such as a test run, find the directories missing until then; runstim worktree warmin the worktree. A build Stim did not run, such as a hand-rungradlew, leaves no record, sogc --deleteand level 2 judge its build directory by the last Stim native run alone and can clear it at once. -
ios/buildis never cleared by gc or maintenance; level 2 used to delete it. -
Dirty finished worktrees are archived, then removed. Their changes go to a new directory under
$STIM_HOME/archive/worktrees/<repo>/, named after the branch or directory, a timestamp and a random suffix, and the removal line,stim logs --source gcand the maintenance log print the restore commands. Thebundle unbundleline appears only when HEAD has commits the default branch lacks:git -C <repository> bundle unbundle <archive>/head.bundlegit -C <repository> worktree add --detach <path> <HEAD>git -C <path> apply <archive>/tracked.patchtar -xf <archive>/untracked.tar -C <path>Set
archive.enabled falseto keep such worktrees instead; that also turns off the workspace history kept on removal. Under a customSTIM_HOME,archive.enabledalready defaults tofalse, so such worktrees are kept there. Ignored files are not archived. -
Clean detached and no-commit worktrees count as finished after 2 hours (
max(gc.worktreeGraceMinutes, maintenance.protectRecentHours)) and are removed bygc --deleteand automatic maintenance.gc --worktrees --older-than Nno longer holds them for N days. Usegit worktree lockon one that must stay. -
Check for a broken cached iOS Debug app from 1.26.0. With React Native 0.88's prebuilt core, 1.26.0's
pod installcould drop the build configuration markers and store a Debug simulator app that dyld kills at launch. A 1.27.0 build no longer produces one, but a 1.27.0 machine that offloads to a 1.26.0 worker still can, because the worker runspod install. The first crashing launch of such a cache hit removes the entry; run the command again to rebuild. A provider's copy stays: build once with--no-build-cache. A worktree whose markers already readDebugover Release binaries is not detected; runrm -rf ios/Pods/React-Core-prebuilt ios/Pods/hermes-engine ios/Pods/ReactNativeDependenciesandpod installthere. Update build workers to 1.27.0 too. -
remote.buildModewas replaced byremote.build.autostaysauto,forcebecomesremoteandoffbecomeslocal. A leftover key is not an error: runs print that it is not read,stim doctorreports it for each file that holds it with thestim settings set remote.build <value>command, andstim settings unset remote.buildModeremoves it from every layer.STIM_REMOTE_BUILD_MODEis removed and runs ignore it without a warning; onlystim doctorreports it. UseSTIM_REMOTE_BUILDinstead, for exampleSTIM_REMOTE_BUILD=localwhere you hadSTIM_REMOTE_BUILD_MODE=off; otherwise the defaultautoapplies and builds can go to a remote machine. In 1.26.0remote.buildModeapplied only whileremote.buildwasauto, so ifremote.buildalready names a machine, keep it and just unsetremote.buildMode. -
The order of
remote.machinesis the placement priority for builds, devices and macOS apps; set it withstim settings set remote.machines '["first","second"]'. Builds no longer prefer the less loaded or warmest machine. -
STIM_BUILD_COMPATstays 2. A 1.27.0 machine and a 1.26.0 machine offload to each other. Update a worker's service withstim-server service update --release 1.27.0to get the worker-side fixes, including thepod installmarker fix. -
Debug iOS simulator builds have no dSYM. Tools that need a dSYM for a Debug simulator build get none; Release and device builds are unchanged.
-
New state under
$STIM_HOME:builds/builds.ndjson,budget-sweep.json,archive/worktrees/, andworkspaces/<id>/lightened.json. Trash entries written byworktree warmcarrykind: warm. Workspace logs gainios_bootandspringboard_crash_reportsrecords and copiedSpringBoard-*.ipsfiles. -
Stim Desktop 0.2.8 needs this release for its Builds section and a machine's build list, which read
stim logs --source builds, and for the Remote Macs order and the Add Machine wizard's build choices, which write placement words 1.26.0 does not know.
QA
The maintainer authorized a stable release without the pre-tag QA gate on 2026-10-25, as for 1.16.0 through 1.26.0. This deviates from RELEASE.md section 3, which has no waiver for a stable version; the expedited RC lane is for prereleases only and was not used. The waiver covers only section 3. No section 3 QA round ran on any commit since v1.26.0, so no change in this release has been through QA, including gc and maintenance lightening and the dependency restore through warm, worktree archives, the no-own-commit worktree rule, the placement vocabulary and remote.machines priority, the build ledger, STIM_TRACE, the dyld cache eviction and pod install marker restore, the SpringBoard restart, the Debug dSYM skip, and the Linux worker script.
Omitted before the tag, for this candidate: all eight rows that node scripts/release-qa-matrix.mjs v1.26.0 requires (287 changed paths with the version commit, 130 mapped to a row, 157 with no native effect, 0 unclassified): loop (expo and bare, iOS and Android), logs (iOS, Android), caches (iOS, Android), real-repository, remote-provider, release-build (iOS, Android), android-flavor and launch-evidence (iOS, Android), plus the native suites test/e2e/native/run-native-e2e.mjs and test/e2e/native/run-cache-e2e.mjs. Not run: test:compat, because the stage's agent-device source checkout (STIM_AGENT_DEVICE_SOURCE) and its validation-owned emulator were not prepared.
Run before the version commit, on the bumped candidate at fe5416bb9, all passing: pnpm install --frozen-lockfile, format:check, lint, build, typecheck, knip, release:prep --check (12 packages at 1.27.0, 23 workspace:* ranges), test:runtime, CLI --version (1.27.0), --help and guide agent, licenses:check and format:check in apps/mobile, and pnpm tarball inspection of the twelve packages (README and LICENSE in each, version 1.27.0, all 23 internal dependencies on exactly 1.27.0 with no ^, ~ or workspace: range; file counts equal 1.26.0's for every package). pnpm test at load average 26 to 41: 437 files, 10929 tests passed and 30 skipped. test:e2e: 119 tests, 118 passed and 1 failed, test/e2e/storage-volumes.e2e.js on macOS, as in 1.24.0 through 1.26.0 (#4205).
Run after the version commit landed: exact-commit CI on main (Node 22 and Node 24 jobs and published runtime (node 22.12.0)), and the Release workflow's own gates. The full Windows unit job is recorded, not gated, per #3878.
1.26.0
Stim Desktop and the Stim phone app ship through their own workflows; this release covers the twelve npm packages (@stim-cli/core, @stim-cli/cache, @stim-cli/cache-s3, @stim-cli/agent-driver, @stim-cli/agent-driver-agent-device, @stim-cli/metro, @stim-cli/expo-build-cache, stim, @stim-cli/ci, @stim-cli/transport, @stim-cli/transport-tailscale and @stim-cli/server). No package publishes for the first time in this release. Commits that only changed those apps, tests, CI or internal structure are not listed.
New
- Automatic maintenance shuts down idle devices that have no dev server. The workspace supervisor already shut down an owned simulator or emulator after
devices.idleShutdownMinutes(30) without activity, but a device with no supervisor, such as after a Release run or a dev server that exited, stayed booted. A new maintenance check, everymaintenance.idleCheckMinutes(new, default 5), shuts those down by the supervisor's own idle rules: booted, no driver, native-run lock, lease, viewer or build, and no activity for the workspace'sdevices.idleShutdownMinutes. It re-checks each device before acting, shuts it down through centralized teardown and never deletes it, andstim statusshowsshut down after <N>m idle. It skips the workspace of the command that started the pass and plans nothing under a scopedSTIM_HOME.stim gcwithout--deletelists the planned shutdowns in its maintenance plan. Stim does not see a person clicking around Simulator.app, so a simulator with no app logs can shut down while someone looks at it. Refs #2598. (stim,@stim-cli/core) 6fe868797 - The maintenance age sweep removes parked devices and, opt-in, idle worktrees. The sweep now deletes parked simulators and emulators unused for
maintenance.olderThanDays(7) through centralized teardown, asgc --older-thandoes; before, maintenance deleted parked devices only under disk pressure. The new machine settingmaintenance.removeIdleWorktrees(defaultfalse, envSTIM_MAINTENANCE_REMOVE_IDLE_WORKTREES) also removes clean, pushed linked worktrees that no Stim command has used formaintenance.olderThanDays, even when their branch never merged, with their git-ignored files; it needsmaintenance.removeFinishedWorktrees. gc's rules still apply: never a dirty or unpushed worktree, the source checkout, or one whose last use is unknown.maintenance.olderThanDays 0is now valid and turns the age removals off; before, 0 was rejected. Refs #2598. (stim,@stim-cli/core) 368c217c1 - A machine-wide log records what gc, maintenance and the budget gate cleared. Every run that deletes or changes something appends one record per item to
$STIM_HOME/gc/gc.ndjson:gc --delete,--idleand--cache(triggermanual, ordesktopfrom Stim Desktop), maintenance actions inonmode (triggermaintenance, with the pass id), and the budget gate's reclaim steps beforestart,iosandandroid(triggerbudget), including the dev servers it stops. Each record haskind,status(done,keptorfailed),label,id,path,bytesanddetail. Dry runs and runs that only kept things write nothing. The log rotates withmaintenance.logMaxMbandmaintenance.logRetentionDays.stim logs --source gcreads it from any directory, with--json,--since,--grep,--tail,--followand--errors;gccannot be combined with other sources and is not part of--source all. Fixes #4253. (stim,@stim-cli/core) 41d117234 stim doctorrecommends app readiness markers. When none of the app's JS or TS sources contain[stim:readiness], doctor adds the notereadiness-markers-missing, pointing tostim guide lifecycle readiness. Without markers a Debug launch ends shortly after the app's JavaScript starts, before the first screen is usable. The scan skipsnode_modules, native and build directories and dot-directories, follows no symlinked directory, and reports nothing past 5000 source files. The guides and website now call the markers recommended instead of optional. Launch verification is unchanged. Fixes #4264. 212721fad- Stim names the layer that stops a remote machine.
stim doctorreported every unreachable remote Mac with one message, and printed the raw WebSocket error. It now stops at the first failing layer and addsdiagnosis { layer, code, title, message, remedy }beside the unchangedstate: Tailscale on this Mac (tailscale-stopped,tailscale-logged-out,tailscale-unavailable), the peer (peer-not-found,peer-ambiguous,peer-offline,peer-no-answer), the serve route (route-missing,route-tls,route-http-error) and stim-server (server-not-running,server-not-ready,server-no-answer,server-outdated,connect-failed).tailscale pingruns only after a silent connection. A peer's/healthnow addsstartup: { state, cause }while the server is not ready, withcausehome-unreadableorstart-failed; the reason text stays loopback-only. Hosted-session errors point atstim doctor. Fixes #4297. (stim,@stim-cli/server,@stim-cli/core) cea2a2cc4 - Android builds offload to a worker that Gradle can download missing SDK packages for. A worker without the project's NDK, build-tools or compile platform was refused. When the worker's SDK licenses are accepted and the project has not set
android.builder.sdkDownload=falsein its Gradle root'sgradle.properties, the missing package is now a note (doctor findingbuild-machine-<code>at levelnote) and the build is offloaded; the Android Gradle Plugin downloads it on the first build, which is not stored as a worker artifact. Otherwise the problem stays a blocker, now withOn <machine>, ...and thesdkmanager --licensesandsdkmanager "<package>"commands at the worker's SDK path. A worker that reports no license field keeps the strict rule.stim-server setupaccepts any Android SDK directory. The note's doctor finding carries thesdkmanagercommand that installs the package ahead of the first build. Fixes #4301 and, for the note's command, #4326. (stim,@stim-cli/server,@stim-cli/core) df356d21d, 1f5287541 - An offloaded Android build runs on an installed JDK of this Mac's major. A worker whose stim-server default JDK had another major was refused with a
JAVA_HOMErestart as the fix. The worker now lists the majors of every JDK it finds (JAVA_HOME,/Library/Java/JavaVirtualMachines, Homebrewopenjdk, Android Studio's JBR, SDKMAN,/usr/lib/jvm, Windows vendor directories; a JRE withoutjavacdoes not count). A client that sees the newbuild-jdkserver feature sends its major onbuild.start, and the worker setsJAVA_HOMEfor that build only. When no installed JDK matches, doctor gives the install command for the worker's OS (brew install --cask zulu@<N>,aptorwinget). The exact-major rule stays. A worker without the field keeps the old rule and text. Fixes #4306. (stim,@stim-cli/server,@stim-cli/core) 302252656 - Doctor diagnoses
xcode-selecton a remote Mac and gives the exact Xcode and runtime commands. The worker's offer on a Mac gainsdeveloperDirand its Xcode apps in/Applicationswith version and build, and, while no Xcode works, thexcodebuild -license checkand-checkFirstLaunchStatusresults. A Command Line Tools selection now readsno Xcode there: xcode-select points at Command Line Toolswithsudo xcode-select -s <app>/Contents/Developerand the license or first-launch command only when needed; no Xcode app givesxcodes install <version>; a missing runtime givesxcodebuild -downloadPlatform iOS -buildVersion <version>. The simulator SDK and runtime problems that follow from the Xcode problem point to it instead of repeating it. A worker that reports nodeveloperDirkeeps the old text. Fixes #4307. (stim,@stim-cli/server,@stim-cli/core) 11ea3cf73 stim-server setupsays what it waits for. After the approval prompts it printsWaiting for the requesting machine to connect. Stim Desktop sends this automatically., then the existingapproved <client> for buildsline when the request arrives. Fixes #4303. (@stim-cli/server) 6524a1cab
Fixes
- Fingerprints use
@expo/fingerprint0.21's balanced preset. A native package is now hashed by name and version instead of by its files, which cuts fingerprint CPU onapps/mobilefrom 3.6 s to 1.6 s on iOS and from 5.2 s to about 1.8 s on Android, and lets a same-version pnpm reinstall with different peer suffixes hit the cache. Every cache key moves once (see Migration notes). Patches at the monorepo install root and thepatchedDependencieshashes inpnpm-lock.yamlare hashed, so a patch outside the app'spatches/still moves the key. A native package's version bump is reported as onechangedsource in the miss line. A hand edit to a native file insidenode_modules/no longer moves the key; use a patch, or--no-build-cache --remote-build local. A project'sfingerprint.config.jscan still choosestrict. Refs #4236. 1025d319d - A workspace's first
stim iosorstim androidreuses a full fingerprint recorded for the same tree. Each full fingerprint is recorded under a static digest of the tree in$STIM_HOME/fingerprint-memo/<platform>/, so a fresh worktree of an already built commit or a new agent session skips the config evaluation and autolinking. The static digest covers the tracked native inputs by git blob id, changed and ignored inputs by content, the config's own sources,.envfiles and the environment variables it reads, every installed package's path, version and native markers, the lockfile and the fingerprint options. A config it cannot read statically, such as one with a dynamicrequireorfsaccess, always takes the full fingerprint. After a hit, a low-priority full fingerprint confirms the record in the background while the app installs; a disagreement prints afingerprintline naming both hashes, writesfingerprint_memo_staleto the build log, drops the record, and leaves the run's artifact in place. The run waits at most 3 s for the check. Only the pre-mutation lookup uses the memo;--no-build-cacheskips it.gcprunes the directory with the other caches. Closes #4209, refs #4236. (stim,@stim-cli/core) a2399ee5f - A cached
stim androidproves the installed APK from its signed tail. Instead of hashing the whole installed APK on the device and the local one in Node (7 to 12 s for a 119 MB APK under load), Stim compares the installed size and the SHA-256 from the v2 or v3 signing block through the end of the file with values recorded beside the cached APK after oneapksigner verify, in oneadb shellcall. A different size or tail installs at once. The whole-file hash remains for APKs outside a build cache entry, APKs without a v2 or v3 block, a missingapksigner, and devices that cannot answer the probe. Android verifies the bytes before the signing block at install, so the only case no longer caught is a rooted write to the installedbase.apk. Fixes #4210. b4a48303b stim iosmutes a running simulator again after SimSlim reboots it. Withios.simslimon or a changedios.simslimProfile, the reconcile on an already-running owned simulator rebooted it and left it at volume 60. Stim now compares SpringBoard's startup pid before and after the reconcile and mutes the simulator when it changed; anAlready slimrun leaves the volume alone, so a Desktop Sound choice survives. When a pid read fails, Stim mutes anyway. The guide and website now say a running simulator is slimmed on the nextstim ios. Fixes #4250. a3880edc6stim iosskips the SimSlim reconcile on an unchanged profile and boot. The owned device record now storessimslimApplied: { profile, springBoardPid }after a reconcile, and a rerun with the same profile content and the same SpringBoard pid skipssimslimand itsapplyingline, saving about 1 s of the preamble. Any other case reconciles as before. A manualsimslim offon a running simulator is noticed only after a reboot or a profile change. Fixes #4289. (stim,@stim-cli/core) 3a9f29156- App readiness applies to hosted iOS and Android launches. On a hosted device (
--remote <machine>or automatic placement), the launch check never saw the app's device log, so[stim:readiness]markers, the early end atRunning "<name>", device-log app errors and hosted Android native crashes were missed. The hosted finishers now copy the slot's log from the host while they verify and use the same verify path as local runs, waiting up to 10 s past each window for a late copy.launched: truestill means a proven bundle request through this workspace's Metro bridge. Local runs keep their timing. Fixes #4270. d317e7798 - A missing or different global CocoaPods on a worker no longer refuses a build sent to a named machine. A named machine (
--remote-build <machine>) now takes the build and reports the difference in the placement line; a failedpod installthere stops withpod install failed: <reason>and an install command for that machine instead of the pairing remedy. Automatic placement still skips such a worker unless it already holds the artifact, andstim doctorreports it withbrew install cocoapodsorgem install cocoapods -v <version>, also when this Mac's own probe finds no CocoaPods. A Bundler pin withoutbundleon the worker is still refused.stim iosnow checks a named machine's toolchain before prebuild and refuses an Xcode, SDK, runtime, CPU, Stim build or Bundler difference with the exact fix, so a mismatch no longer costs a prebuild andpod installfirst. stim-server rediscovers Homebrew, the JDK and the Android SDK for each build offer and build, so a tool installed after the server started is found. Fixes #4322. (stim,@stim-cli/server) 102fa597b - Slow Desktop reports no longer fill a connection's request slots on stim-server. A waiting
desktop.reports.getheld one of the connection's four request slots, and an invalidation left the stale load running, so a gc report could time out and every other request was refused withlimit-exceeded. Report waits now use their own pool of 16 per connection, an invalidation aborts the stale load, and behind the new local featuredesktop-report-invalidate-targetan invalidation can name one report target. The per-connection request limit rises from 4 to 32, and a tailnet device can run at most 16 requests across its connections until its cancelled work drains. Fixes #4298. (@stim-cli/server,@stim-cli/core) a9e18e635 - The gc and doctor reports Stim Desktop polls are cheaper. gc reads the worktree inventory up to six at a time (one at a time while an operation declares its children on a claim, as
gc --deleteand maintenance do), and stim-server's gc report reads worktree build-state sizes only from the disk-usage cache instead of runningdu. The in-process doctor reuses its iOS architecture inspection for 10 minutes while the project files, xcconfigs,Podfileand lock files are unchanged, so a polled doctor does not rerunxcodebuild -showBuildSettings. On the maintainer's home a warmstim gc --jsonwent from 58 s to 42 s.gc --deletedecisions are unchanged. Fixes #4300. (stim,@stim-cli/server) 9402c39d6 stim stopnames every retained claim when several device teardowns fail. Since devices tear down concurrently, two teardowns could each retain an ownership claim, and only the first was reported.stopnow throws one error that names every retained claim with the command that removes each; a cleanup error is reported ahead of any other rejection. Fixes #4249. be8cc4e47stim worktree removereports the trash as deleted when its backgroundrmexits before it is recorded. It printedleft 2 ignored directories in <dir>; stim gc --delete deletes themalthough they were already gone. It now reportsdeletedwhen the trash entry is gone andleftonly when it remains. Fixes #4267. 264e9b5a1
Migration notes
- Update the client Mac and its build workers together again.
STIM_BUILD_COMPATgoes from 1 to 2 because every fingerprint key moves. A 1.26.0 machine and a 1.25.x machine report different compatibility versions, so they never offload to each other: automatic placement builds locally, and a named build machine refuses withSTIM_OFFLOAD_REFUSED. Update a worker's service withstim-server service update --release 1.26.0. - Every build cache key moves once. The first
stim iosandstim androidof each project, platform and variant after the update misses the cache and builds. Entries stored by earlier versions stay until gc ages them out. - A hand edit inside
node_modules/no longer changes the cache key. Use a patch, which is hashed, or build with--no-build-cache --remote-build local. - Automatic maintenance does more by default. With
maintenance.mode on(the default outside a scopedSTIM_HOMEand CI), owned devices without a dev server shut down afterdevices.idleShutdownMinutes(30), and parked devices unused formaintenance.olderThanDays(7) are deleted. Setdevices.idleShutdownMinutes 0, for one project or the whole machine, to keep devices running.maintenance.olderThanDays 0keeps parked devices out of the age sweep and also turns off the sweep's other age removals, old build outputs and shared cache entries; disk pressure level 2 still deletes parked devices, andmaintenance.deviceIdleDays(3) still deletes the owned devices of an idle workspace, as before. Idle unmerged worktree removal stays off unlessmaintenance.removeIdleWorktreesis set. - New state under
$STIM_HOME:gc/gc.ndjsonand its rotated generation, andfingerprint-memo/<platform>/. Android build cache entries gainapk-signed-tail.json, written on first use for older entries. The owned iOS device record gainssimslimApplied. - stim-server request limits changed. A local client can run 32 requests per connection instead of 4; a tailnet device is limited to 16 across its connections. Report waits have their own limit of 16 per connection.
- Stim Desktop 0.2.7 needs this release for its Automatic Maintenance settings, Cleanup History and the remote machine diagnosis; with an older
stimthose parts ask to update it.
QA
The maintainer authorized a stable release without the pre-tag QA gate on 2026-10-22, as for 1.16.0 through 1.25.0. This deviates from RELEASE.md section 3, which has no waiver for a stable version; the expedited RC lane is for prereleases only and was not used. The waiver covers only section 3. No section 3 QA round ran on any commit since v1.25.0, so no change in this release has been through QA, including the idle-device shutdown and parked-device age sweep in automatic maintenance, the gc log, the @expo/fingerprint 0.21 keys and the fingerprint memo, the signed-tail APK proof, the SimSlim mute and skip, hosted readiness, and the offload changes on a second Mac.
Omitted before the tag, for this candidate: all eight rows that node scripts/release-qa-matrix.mjs v1.25.0 requires (246 changed paths with the version commit, 93 mapped to a row, 153 with no native effect, 0 unclassified): loop (expo and bare, iOS and Android), logs (iOS, Android), caches (iOS, Android), real-repository, remote-provider, release-build (iOS, Android), android-flavor and launch-evidence (iOS, Android), plus the native suites test/e2e/native/run-native-e2e.mjs and test/e2e/native/run-cache-e2e.mjs. Not run: test:compat. The test iPhone was connected, but the stage's agent-device v0.21.28 source checkout and its validation-owned emulator were not prepared.
Run before the version commit, on the bumped candidate: format:check, lint, build, typecheck, knip, release:prep --check (12 packages at 1.26.0, 23 workspace:* ranges), CLI --version (1.26.0), --help and guide agent, licenses:check and format:check in apps/mobile, and pnpm tarball inspection of the twelve packages (README and LICENSE in each, version 1.26.0, all 23 internal dependencies on exactly 1.26.0 with no ^, ~ or workspace: range). File counts equal 1.25.0's except stim, 208 instead of 209: only the hashed dist/ chunk files differ, and every file outside dist/ is the same. pnpm test ran 10630 tests on a Mac at load average 100 to 300: 10567 passed, 29 skipped, 34 failed in 12 files, 27 of them by timeout. The 12 files rerun alone, while the load reached 600, failed 26 cases in 7 files, again mostly timeouts. The two that were not timeouts are a vi.waitFor window in packages/server/__tests__/device-host.test.ts (installing where installed was expected) and one in packages/server/__tests__/server.test.ts (503 where 200 was expected). The failing cases differed between the two runs. The local unit result is therefore not a pass; the full unit suite is left to exact-commit CI. The unit suite ran on the candidate before main gained 1f5287541. After that commit was merged into the candidate, these ran on it and passed: every check listed above, the tarball inspection (unchanged results), test:runtime, and the two unit test files that commit changes (173 passed). test:e2e ran only on it: 119 tests, 118 passed and 1 failed, test/e2e/storage-volumes.e2e.js on macOS, as in 1.24.0 and 1.25.0 (#4205).
Run after the version commit landed: exact-commit CI on main (Node 22 and Node 24 jobs and published runtime (node 22.12.0)), and the Release workflow's own gates. The full Windows unit job of a dispatched CI run on the candidate is recorded, not gated, per #3878.
1.25.0
Stim Desktop and the Stim phone app ship through their own workflows; this release covers the twelve npm packages (@stim-cli/core, @stim-cli/cache, @stim-cli/cache-s3, @stim-cli/agent-driver, @stim-cli/agent-driver-agent-device, @stim-cli/metro, @stim-cli/expo-build-cache, stim, @stim-cli/ci, @stim-cli/transport, @stim-cli/transport-tailscale and @stim-cli/server). No package publishes for the first time in this release. Commits that only changed those apps, tests, CI or internal structure are not listed.
New
- Offloaded builds and worker peer artifacts compare a Stim build compatibility version instead of the exact Stim build (stim-server feature
build-compat). Before, a client and a build worker had to run byte-identical Stim builds, so an npm release on one Mac and a checkout build on the other, or any two different commits, could not offload to each other. Eachstimnow carriesSTIM_BUILD_COMPAT(1 in this release) in every toolchain it reports, and two machines that both report it build for each other when the values are equal. The value changes only in a release that changes what a worker builds, how it builds it, or the build and artifact messages. A client sendscompatonbuild.start, and in place ofstimBuildin React Native and SwiftPM peer artifact queries, only to a stim-server that advertisesbuild-compat; against a server without it, peer artifact queries are skipped. A worker update that keeps the same compatibility version keeps its completed artifacts, and worker peer entries stored before this release miss once. A machine that reports no compatibility version still needs the exact same Stim build. Doctor and placement keep thestim-buildproblem code and its reason text, so Stim Desktop's update path is unchanged. The local build cache key does not change. Fixes #4206. (stim,@stim-cli/server,@stim-cli/core) 91e3b90aa ios.simslimslims every Stim-owned iOS simulator without a profile per app. The new setting takesonoroff, defaultoff, at every layer. Withstim settings set ios.simslim on --scope machine,stim iosrunssimslim onwith SimSlim's default categories on each owned simulator it boots, adopts or finds already running, through the same path as a profile. On a simulator it boots, the mute runs after SimSlim's reboot. A projectios.simslimProfilestill applies in place of the defaults, andios.simslimset tooffin a workspace, repo or committed layer opts that app out and restores stock services on a simulator Stim slimmed. The first slim reboots the simulator once, and a simulator that was already running stays unmuted after that reboot, as with a profile change (see the SimSlim mute fix below, and #4250). An already slim one is left alone. Without thesimslimcommand,stim iosprints a one-line note and boots unslimmed, andstim doctorreportsios.simslim is on, but SimSlim is not installed. Hosted simulators on a named Mac are not slimmed. SimSlim's defaults disable push, StoreKit, iCloud, Safari sync and the Mail, Calendar and Contacts services among others, so an app that needs one should use a profile that keeps it, oroff. Fixes #4242. (stim,@stim-cli/core) 158350087
Fixes
- An owned iOS simulator stays muted after SimSlim reboots it. With an
ios.simslimProfile,stim iosmuted the simulator right after boot and printedsimulator muted, then applied the profile withsimslim on, which shuts the simulator down and boots it again. SpringBoard sets its default volume of 60 on that boot, so the simulator played sound.simslim offreboots the same way. Stim now mutes after SimSlim has applied or removed its profile, and waits up to 60 seconds for SpringBoard to finish starting first. When the wait fails or times out, the run printsCould not mute this simulatorand continues. A simulator that is already running is still not muted, so a profile change on a running simulator leaves it at the volume SimSlim's reboot set. Fixes #4229. f41fc2b98 - Every package depends on the other Stim packages at the exact release version. The 1.24.0 tarballs named internal dependencies such as
@stim-cli/coreas^1.24.0, so installing an olderstimor@stim-cli/servercould resolve a newer@stim-cli/coreand run code from two versions together. From 1.25.0 everystimand@stim-cli/*dependency in the published manifests is the bare version,pnpm run release:prep --checkaccepts onlyworkspace:*ranges, and the Release workflow refuses a packed manifest with a^or~internal range. Fixes #4215. (every package with an internal dependency) 097cbc63c - Launch verification waits while an Expo dev client's manifest request is pending. An Expo dev client asks the dev server for its manifest before its bundle, and with the fingerprint runtime version policy Expo recomputes the fingerprint for every manifest request, so on a loaded machine a slow launch could end
launched: "unverified"after the 20-second window. On Expo SDK 54 and later, where Stim injects its Metro config, Stim's Metro middleware now records each dev-client manifest request inmetro.ndjsonasmanifest_request_started,manifest_request_finishedormanifest_request_failed(debug level, withplatformandrequestId). Verification keeps waiting while one is in flight and for 15 seconds after one was served, up to 60 seconds after the launch. A manifest request is progress, not launch evidence:trueand"bundling"still need the app's bundle request, and a failed manifest request extends nothing. Bare React Native, and Expo SDK 53 and older, keep the 20-second window. Fixes #4207. 7c72fc4b9 - The launch stability window ends once the app's JavaScript is running. After the bundle response, verification waited a fixed 3 seconds before reporting the launch stable. It now ends half a second after the app's first
Running "<name>"log for this launch, on iOS and Android, or at the 3-second cap. The verify line then readsapp JavaScript running -- the first screen may still be renderinginstead ofstable for 3s. A fatal record or an exited process before the window ends still fails the launch; an error after it appears instim logs --errors. Fixes #4225. c26b9cac0 - The Metro prefetch uses the bundle URL the app last requested. On an Expo app, the prefetch during
stim iosandstim androidfetched the manifest to learn the bundle URL, which ran one more Expo fingerprint under the fingerprint runtime version policy and delayed the prefetch until it finished. Thebundle_response_startedrecord of an app's bundle request inmetro.ndjsonnow carries itsurl, and the prefetch uses the newest recorded URL for the platform when it passes the same check asmetro.warmupUrl.<platform>and its entry file still exists. Order:metro.warmupUrl.*, then the recorded URL, then the Expo manifest or the bare React Native default. A bare React Native app that has served a bundle also prefetches its recorded URL instead of the default index bundle. A workspace that has never served a bundle still fetches the manifest or uses the default once. Fixes #4226. 65605ae9e stim stopshuts down a workspace's owned simulator and emulator concurrently. It waited for each device's teardown before starting the next, so a workspace with both paid the simulator shutdown on top of the emulator'sadb emu kill, which waits for the quickboot snapshot. The teardown itself, with its ownership re-checks, is unchanged. Results are still reported in the same order once every device has settled, so thedevicelines and thestop --jsonpayload keep their order; progress lines can now come before them. When one teardown throws, the others still finish and are reported before the error propagates. Fixes #4243. ec621a003stim iosrestarts a running simulator app with onesimctl launch --terminate-running-process. It ran a separatesimctl terminatefirst. Therestarted running app (was pid N)note and launch verification are unchanged. A dev client opened through a URL still terminates first, and Android is unchanged. Fixes #4235. 03eee3e24stim ioslists simulators, runtimes and device types with onesimctl list --jsoncall. A warm run made fivesimctl listcalls before selecting a simulator. Boot, teardown and the ownership re-checks still list live. Fixes #4228. f7fa3baf5- A build cache hit reuses the stored bundle id or Android package. A cache entry now stores the app id read from its artifact, so a hit skips the
plutilread of the bundle id on iOS and theaaptread of the package on Android. Entries stored before this release keep the read. Refs #4209. 49526c12e stim worktree warmdeletes known problem paths from copied directories instead of walking the whole copy. After copying a directory it now checks only.pnpm-storeandexpo-modules-jsi/apple/.DerivedDatainsidenode_modules(including pnpm's virtual store), andandroid/build/generated/autolinking. Whole ignored entries named.jj,.pnpm-store,.DS_Store,.ideaor.DerivedDataare still not copied, but.DS_Store,.ideaand other.DerivedDatadirectories nested inside a copied directory now stay in the copy. A delete whose parent resolves outside the copied entry, such as through a copied symlink, is skipped. Fixes #4216. 2bd8c3281
Migration notes
- Update the client Mac and its build workers together. A 1.25.0 machine and a 1.24.x machine never offload to each other: the older side reports no build compatibility version, so both fall back to the exact Stim build match, which differs. Doctor reports
stim-build. Until both run 1.25.0, automatic placement builds locally, and a named build machine (--remote-build <machine>,STIM_REMOTE_BUILDorremote.build) refuses withSTIM_OFFLOAD_REFUSED, as does automatic placement withlocaldisabled inremote.buildPoolDisabled. Update a worker's service withstim-server service update --release 1.25.0. After that, later releases and checkout builds with the same compatibility version offload to each other. - Internal dependencies are exact pins.
@stim-cli/expo-build-cacheor@stim-cli/metroinstalled beside a differentstimversion now brings its own copy of@stim-cli/coreat its own version instead of sharing one through a^range. Nothing fails to resolve; the cost is a second@stim-cli/coreon disk. metro.ndjsonhas new records and fields.manifest_request_*records appear in Expo workspaces' metro logs, andbundle_response_startedgains aurlfield in every workspace. Readers of the log should ignore unknown events and fields.
QA
The maintainer authorized a stable release without the pre-tag QA gate on 2026-10-21, as for 1.22.0 through 1.24.0. This deviates from RELEASE.md section 3, which has no waiver for a stable version; the expedited RC lane is for prereleases only and was not used. The waiver covers only section 3. No section 3 QA round ran on any commit since v1.24.0, so no change in this release has been through QA, including the build compatibility version on two Macs, the SimSlim mute, the manifest-aware launch window, the early end of the stability window, the recorded prefetch URL, the warm deletes, the concurrent stim stop and ios.simslim.
Omitted before the tag, for this candidate: all eight rows that node scripts/release-qa-matrix.mjs v1.24.0 requires (118 changed paths with the version commit, 58 mapped to a row, 60 with no native effect, 0 unclassified): loop (expo and bare, iOS and Android), logs (iOS, Android), caches (iOS, Android), real-repository, remote-provider, release-build (iOS, Android), android-flavor and launch-evidence (iOS, Android), plus the native suites test/e2e/native/run-native-e2e.mjs and test/e2e/native/run-cache-e2e.mjs. Not run: test:compat, which needs a connected iPhone.
Run before the version commit, on the bumped candidate: format:check, lint, build, typecheck, knip, release:prep --check (12 packages at 1.25.0, 23 workspace:* ranges), test:runtime, CLI --version (1.25.0), --help and guide agent, licenses:check and format:check in apps/mobile, and pnpm tarball inspection of the twelve packages (README and LICENSE in each, version 1.25.0, all 23 internal dependencies on exactly 1.25.0 with no ^, ~ or workspace: range, file counts equal to 1.24.0's). pnpm test ran 10380 tests on a Mac at load average 140 to 230: 10342 passed, 29 skipped, 9 failed in 5 files. Rerun alone, packages/stim-cli/src/__tests__/doctor-cxx.test.ts, packages/server/__tests__/mobile-protocol.test.ts and packages/stim-cli/src/__tests__/hosted-log-discovery.test.ts passed. packages/server/__tests__/device-host.test.ts failed one or two different cases on each of two reruns at load average 180 to 380. packages/server/__tests__/machine-update.test.ts fails on macOS because a darwin-only case still expects a ^ range for a packed internal dependency, which 097cbc63c made exact (#4245); CI's unit jobs run on Linux and skip it. The local unit result is therefore not a pass; the full unit suite is left to exact-commit CI. test:e2e ran 118 tests: 117 passed and 1 failed, test/e2e/storage-volumes.e2e.js on macOS, as in 1.24.0 (#4205). Those runs were on the candidate built from 03eee3e24. After main gained 65605ae9e and the candidate was updated, the build, CLI --version and the three unit test files it changes plus bundle-response.test.ts (1007 passed) were rerun. After main gained 158350087 and it was merged into the candidate, these were rerun on it and passed: format:check, lint, build, typecheck, knip, test:runtime, release:prep --check, CLI --version, --help and guide agent, licenses:check and format:check in apps/mobile, the tarball inspection (unchanged results), and the four unit test files that commit changes (426 passed). The full pnpm test also ran again on it, at load average 120 to 140: 10367 passed, 29 skipped, 2 failed, the machine-update.test.ts case above and one hosted-log-discovery.test.ts case, which passed when its file was rerun alone (5 of 5). test:e2e was not rerun on it. After main gained ec621a003 and it was merged into the candidate, stop.test.ts (103 passed) and the checks listed above, apart from the unit and e2e suites, were rerun on it and passed. After main gained the test-only 6268767ad, which fixes the stale expectation from #4245, and it was merged into the candidate, machine-update.test.ts passed on it (5 of 5).
Run after the version commit landed: exact-commit CI on main (Node 22 and Node 24 jobs and published runtime (node 22.12.0)), and the Release workflow's own gates. The full Windows unit job of a dispatched CI run on the candidate is recorded, not gated, per #3878.
1.24.0
Stim Desktop and the Stim phone app ship through their own workflows; this release covers the twelve npm packages (@stim-cli/core, @stim-cli/cache, @stim-cli/cache-s3, @stim-cli/agent-driver, @stim-cli/agent-driver-agent-device, @stim-cli/metro, @stim-cli/expo-build-cache, stim, @stim-cli/ci, @stim-cli/transport, @stim-cli/transport-tailscale and @stim-cli/server). No package publishes for the first time in this release. Commits that only changed those apps, tests, CI or internal structure are not listed.
New
device-host.metro.ports(featurehosted-metro-ports): a hosted iOS Expo dev-client run picks a Metro port that is free on both Macs. An Expo dev client loads its bundle from the port that the client's dev server writes into the manifest, so the hosting Mac's session bridge has to listen on the client's Metro port (see the matching Fixes entry). Both Macs allocate Metro ports upward from 8082, so binding the bridge on the client's port would often be refused withSTIM_HOSTING_REFUSEDatdevice-host.metro.open, after the build and the simulator boot, because the host already uses that port. Before it starts Metro for an Expo app that has anexpo-dev-clientdependency on a hosted iOS Debug run (named host or auto placement),stimnow asks the host which of a window of 64 candidate ports already accept loopback connections ({ ports }in,{ busy }out, at most 256 ports, device-host approval only) and reserves a port that is free on both. A recorded workspace port the host uses is moved with the existing "Reserved port N for this project instead" note. A Metro that is already running, or ametro.port/STIM_METRO_PORTpin, is never moved: if the host uses that port the run refuses withSTIM_HOSTING_REFUSEDbefore the build, a pinned Metro is not started, and the remedy says to runstim stop, thenstim ios --remote <machine>withoutstim start, or to pin a port free on both Macs. Ports held by the same client's own dev-client bridges are not reported busy, so a rerun against a resumed session does not conflict with itself. When the host cannot answer, the run notes that it could not check the host and picks a port free on this Mac; when the host does not advertisehosted-metro-ports, it does the same without a note. Either way, the host's own bind check still refuses an occupied port. Fixes #4096. (stim,@stim-cli/server,@stim-cli/core) f075a6acfremote.easMaxDurationMinutes(machine setting, positive integer, default 30) caps each EAS Simulator session Stim creates. Stim passes it toeas simas--max-duration-minutesfor--remote easandremote.easFallback, so EAS stops the session and stops billing after that many minutes even when nobody ranstim stop(for example when the workspace was deleted by hand). Before, Stim never passed the flag, so a session ran until the EAS account plan's own limit. A session that expired is replaced by the next run; a run that reuses a live session does not check how much of the cap is left;stim stopandgc --deletestill end a session sooner. EAS rejects a value above the account plan's limit, and that rejection appears as theeas sim failedreason. An invalid value in the machineconfig.jsonrefuses withSTIM_BAD_ARGinstead of creating an uncapped session. There is no flag or environment override. Fixes #3739. (stim,@stim-cli/core) 8e764f689- A
fingerprint-mismatchrefusal from a build worker names the sources that differ and the files the worker rewrote. Before, a React Native or Expo offload that the worker fingerprinted differently failed with only the two truncated hashes (the checkout there fingerprints <12>, here <12>). The reason now also lists, when the worker rewrote synced files,expo prebuild and pod install there rewrote <files>and, when the build Mac's stim-server advertisesfingerprint-sources,differing sources: <names>(three names, thenand N more). TheSTIM_OFFLOAD_REFUSEDremedy for a named--remote-build <machine>now says to make the named input agree on both Macs (for example by committing the lockfilepod installrewrote there) instead of the generic pairing remedy. The client sends each fingerprint source'sidandhashasfingerprintSourcesonbuild.startonly to servers that advertise the new featurefingerprint-sources. (stim,@stim-cli/server,@stim-cli/core) 50ae9e043
Fixes
- A build worker installs an app's dependencies with the lockfile nearest the app, installs the whole pnpm workspace, and reuses a hoisted install. The worker finds the first
pnpm-lock.yaml,yarn.lockorpackage-lock.jsonin the app's directory or above it in the checkout, and runs the install in the directory that holds it:pnpm install --frozen-lockfile,yarn install --frozen-lockfileornpm ci. Before, it looked for a lockfile only at the repository root, so an app with its own lockfile inside a larger repository failed prebuild on the worker, and a pnpm install was filtered to the app and its dependencies (--filter <package>...), which missed workspace packages that a Gradle task runs (for examplepnpm run --filter <package>). Thedepsprogress line addsin <dir>when the install is not at the checkout root. An install in a subdirectory keeps its own marker (dependencies-<hash>), and the worker skips the install when the lockfile, install command and Node major match the marker andnode_modulesexists in the install directory. Before, it requirednode_modulesin the app's own directory, so apps whose dependencies are hoisted to the workspace root were reinstalled on every build. The client computes the lockfile hash it sends for dependency ranking from the same nearest lockfile. Fixes #4070, #4094 and #4091. (stim,@stim-cli/server,@stim-cli/core) 6000dbc12, f62250932, 29dc5e251 - An offloaded React Native or Expo build sends the gitignored files its fingerprint reads, so the worker computes the same fingerprint. Besides the tracked and untracked files git lists, the client now also uploads the gitignored files that the native fingerprint hashes (under fingerprint sources inside the repository, skipping
node_modulesand paths the fingerprint itself ignores) and, when the Expo config is a fingerprint source, the Expo env files that exist (.env,.env.local,.env.<mode>,.env.<mode>.local, unlessEXPO_NO_DOTENVis set). Before, an app whose config reads a gitignored.env.localwas refused withfingerprint-mismatch. These files can hold secrets and now go to the paired worker; nothing undernode_modulesand no build output the fingerprint ignores is sent. Stim's own fingerprint (and so the build cache key) now also ignores Xcode's*.xcodeproj/xcuserdataand, for iOS, thereact-native bundleoutputios/main.jsbundleandios/assetswhile a.gitignorepattern matches them (even when committed);stim doctor's fingerprint-parity remedy and the.fingerprintignoreguide text say so. Fixes #4092. (stim,@stim-cli/server,@stim-cli/core) 50ae9e043 - A build worker ignores the same iOS fingerprint paths the client's git ignores or tracks. The worker has only the synced
.gitignorefiles and an empty git index, so it disagreed with the client for paths ignored through.git/info/excludeor a global excludes file, and for tracked top-level entries that a.gitignorepattern matches. For an iOS build the client now sendsfingerprintIgnored(the bundle output and, withios.projectPathset to., the top-level entries its git ignores) andfingerprintTracked(the top-level entries it tracks although a pattern matches them,ios.projectPath.only) onbuild.start, only to servers that advertise the new featuresfingerprint-ignoredandfingerprint-tracked, and the worker applies them in its initial, post-prebuild and final fingerprints. Before, both cases ended infingerprint-mismatchorfingerprint-moved. No extra files are uploaded for these two. Fixes #4110 and #4152. (stim,@stim-cli/server,@stim-cli/core) 5caf777a7, e23e90475 pod installand the CocoaPods version check honor a Gemfile pin inios/. Stim now takes the nearestGemfilefrom the directorypod installruns in (ios/) up to the project root, as Bundler does. When itsGemfile.lockresolvescocoapods, pods run throughbundle exec pod installon the client, on the worker and in thestim doctorandstim worktree warmcommands it prints, and thebundle installremedies tell you tocdto the Gemfile's own directory. Before, only a rootGemfilecounted, so an app with its pin inios/Gemfileran plainpod install, a different CocoaPods could rewritePodfile.lockon the worker, and the offload ended infingerprint-mismatch. For a project without a pin, this Mac'spod --versionnow runs in the iOS directory; for a pinned one, Stim reports the version the lockfile resolves and requires only Bundler on the worker. A nearerGemfilethat does not pincocoapodsnow hides a pin in a parent directory. Fixes #4112. 22b4bb61d- A
.ruby-versionthat names an rvm Ruby gets that Ruby's@globalgemset and gemsetbindirectories.pod installand the CocoaPods probe now setGEM_PATHto the Ruby's gemset plus@global, and put the gemset and@globalbindirectories onPATH, asrvm usedoes. Before, only the Ruby's ownbinwas added, so on a Mac whose CocoaPods dependencies (such asmutex_m) live in@global,pod --versionfailed and a worker reported no CocoaPods, which the client showed as "no CocoaPods there". rbenv, asdf and mise are unchanged. Fixes #4114. 81f027b4e stim macos --remote <machine> --remote-build <machine>no longer requires this Mac's Xcode, macOS SDK or CPU architecture to match the worker's when both name the same Mac. The app is built and run on that Mac, so the client skips those three checks and now requires only that the worker has Xcode and a macOS SDK and runs the same Stim build. The client's own completed-app cache is skipped, a cache hit is validated against the worker's architecture, and the worker's cache identity comes from its own toolchain. "Same Mac" means the build machine's pinned server key equals the host's. Every other combination, including--remote-build auto, keeps the full match. Fixes #4099. 08ae7f7bd- A successful hosted Android build is recorded as
ok.stim android --remote <machine>wrote the last-build statussuccess, which the status readers rejected, sostim status, Stim Desktop and the phone app showed no Android last build or history entry for it. It now writesok. Records already on disk withsuccessread asok(historysucceeded) without being rewritten. Fixes #4183. (stim,@stim-cli/core) 7fd32a463 - A
stim iosorstim androidrun that builds or reuses an app and then fails records a failed build. Failures after the artifact exists (a simulator or emulator that does not boot,STIM_NO_DEVICE, a hosted placement refusal, a device lease refusal, a hosted launch failure, or a thrown error) now leave afailedentry inlastBuildsandbuildswith the error code and the cache facts (fingerprint,cacheKey,cacheHit,builtOn,missReason),appPathandbundleId. Before,stim statscounted the run as failed butstim status, Desktop and the phone app kept showing the previous build. The record shape is unchanged. The run also writes one error-levelbuildlog record with eventrun_failed, its code and the phase it failed in, sostatusandlogs --errorsagree; a run that fails before it has an app writes none. Fixes #4137. 8f57feec9, dbd1266c0 - A hosted iOS Expo dev client loads its JavaScript. Expo's dev server writes its own port into the manifest's
launchAsset,hostUrianddebuggerHost, so a dev client launched through the host's session bridge asked127.0.0.1:<client Metro port>on the hosting Mac, where nothing listened, and stayed atlaunched: "unverified"with a redbox. For a hosted iOS run of an Expo dev client, the client now sends its Metro port withdevice-host.metro.open(it already did for Android) and the host binds the bridge on127.0.0.1:<that port>instead of a random one. The host first probes by connecting to that port on127.0.0.1and::1, because macOS lets a127.0.0.1bind succeed beside a wildcard listener and take its traffic. If the port already accepts connections,device-host.metro.openis refused withdevice-busy, andstimreportsSTIM_HOSTING_REFUSEDwith a remedy that namesmetro.portandSTIM_METRO_PORT. Other iOS runs (bare React Native, non-dev-client Expo) send no port and keep a random bridge port. Fixes #4069. Both Macs need 1.24.0; see Migration notes. (stim,@stim-cli/server) 6edb8944d stim reloadreaches an app on a physical device or an EAS Simulator session through the workspace's Metro. Stim records launches only on owned simulators and emulators, so for an app launched withios --device,android --deviceor--remote eas,reloadrefused withSTIM_RELOAD_STOPPED("runstim iosfirst"), and following that only reinstalled the app and refused again. When no owned launch is live,reloadnow asks this workspace's Metro for its connected apps and reloads the matching ones: iOS apps by theirrole=ios, and on Android every non-iOS app, because Stim has no package name for these launches. When Metro lists its clients, it sends nothing unless one matches; the bare React Native dev server cannot list them, so there the reload is broadcast to every connected app and reported asstrategy: "metro-broadcast". A barestim reloadtakes the platform the connected apps report; when Metro cannot name its apps, it takes the recorded EAS session's platform if there is one. It refuses withSTIM_RELOAD_AMBIGUOUSwhen both platforms are connected, or when Metro cannot name its apps and no EAS session is recorded. In--jsonand the programmatic API'sStimReloadResult,appIdisnullfor such a reload,deviceIdismetro:<port>, anddeviceNameisdevices connected to Metroor the EAS session. With no connected app it still refuses withSTIM_RELOAD_STOPPED; for an EAS session the remedy isstim <platform> --remote eas, which relaunches the app, because an app in an EAS session may not open Metro's websocket through the tunnel.reloaddoes not restore a lostadb reverseon an Android phone. The physical-device launch remedies now saystim reload ios|androidinstead ofagent-device metro reload, which iOS keeps only for a--remotedevice. An owned launch keeps the existing path. Fixes #4133 and #4201. 750c5901d, 6771f8539stim reload iosno longer fails withSTIM_RELOAD_PROBE_FAILEDwhen the process probe is slow. Before it sends the Metro reload,reloadchecks that the app process exists; on iOS that issimctl spawn <udid> launchctl listwith a 2 second timeout, which a loaded Mac regularly exceeded. The iOS probe now waits 5 seconds scaled up with load per CPU, to at most 30 seconds. A probe that still returnsunknown(iOS or Android) no longer refuses: the reload is sent, andSTIM_RELOAD_PROBE_FAILEDis reported only when the Metro reload then fails and a second probe still cannot tell whether the app runs. A process the probe proves gone still returnsSTIM_RELOAD_STOPPED. Fixes #4165. 1e1404388stim stopandworktree removeremove the Metroadb reversethatandroid --deviceadded on a physical phone. The reverse outlived the workspace, so once Stim gave that Metro port to another workspace, apps on the phone that openedlocalhost:<port>reached that other server. Before eachandroid --devicelaunch against Metro, the run records the phone's serial and the Metro port in the workspacestate.json.stop(all slots, or only--slot's phone),worktree removeandgc --deleteof the workspace listadb reverseon the recorded serial, remove onlytcp:reverses whose host side is a recorded port, and drop the record; every other reverse on the phone stays. An unreachable phone is skipped without a message and its record is dropped, so a laterstopdoes not retry it; neither case fails the stop. Whenstoprefuses to signal an unverified supervisor or dev server, the reverse and its record stay. A slot whose--devicerun moves to a different phone keeps the previous phone's reverse until that phone is unplugged or the adb server restarts. Fixes #4132. 7d81956a5stim stopremoves an owned emulator's Metroadb reversebefore it stops Metro. After Metro was killed, the app kept reconnecting through the leftover reverse; on Windows the adb server connects each such stream with a blocking loopback connect (about 2 seconds when refused), so the retries stalled every adb call and the teardown'sadb devicestimed out.stopnow removes the reverses that point at the workspace's Metro port on each owned, running emulator (confirmed throughemu avd name, including thetcp:8081fallback reverse) right before it signals the supervisor or a verified leftover dev server, or when it finds nothing listening. An unverified supervisor or dev server is not signalled and its reverses stay. The removal is best effort and bounded; a failure leaves the existing teardown unchanged. A physical phone or a non-owned emulator is not covered by this entry. Fixes #4101. 71a9f97aestim logs --errorsno longer returns every OS log line from a hosted iOS app. Hosted pulls stamped each recordevent: "hosted_native_log", and the query admitted that event wholesale, so a healthy hosted iOS launch showed about 100 UIKit and Core Animation lines under--errors, andstatuserror counts read the same criteria. A local simulator keeps those lines outside--errorsand shows them with--source device; hosted records now follow the same rule, and none are dropped (they appear with--source deviceor--source all). So that a hosted Android crash still appears, the hosted pull groups logcat per process from each crash header and stamps a group matchingFATAL EXCEPTION:orsignal N (asnative_crash, using the same crash-header patterns and grouping as the local crash capture, without its app-id check. Hosted iOS produces nonative_crash. Each line of a crash run is its ownnative_crashrecord, and a crash run split across two pulled pages keepsnative_crashonly on the page with its header. Fixes #4136. (stim,@stim-cli/core) 48ee630castim ios|android --remote eastells an Expo app with no EAS project to link one. Wheneas simreports "EAS project not configured" (noextra.eas.projectId), the run failed aseas sim failed: ...and thenSTIM_NO_DEVICEwith "runstim iosagain", which fails the same way. It now returnsSTIM_REMOTE_EAS_UNAVAILABLEwith the reason "The Expo app is not linked to an EAS project, so no EAS Simulator session was created." and a remedy namingeas init --id <project-id> --non-interactive(existing project) oreas init --account <account-name> --non-interactive(new project), then thestim <platform> --remote eascommand to rerun. No session is created. Fixes #4135. e7ead70eb- Starting Metro retries when another Stim home takes the reserved port. Two Stim homes on one machine keep separate port reservations and can pick the same free port; the second dev server then failed to bind and
startended withSTIM_SUPERVISOR_EXITEDandlisten EADDRINUSE ... :<port>. When the supervisor exits with that bind error on the port Stim reserved,start, and the Metro start insidestim ios|android, now release the reservation, print "Port N was taken by another process while the dev server started." and start again on another free port, up to 3 attempts; after the third it fails with the original code and a remedy naming the port. A pinnedmetro.port/STIM_METRO_PORT,--remotestarts, and any other supervisor exit are not retried. Fixes #4073. 65a6b77c0 stim worktree warmclones a carried directory in one step on macOS. For an ignored entry that is a real directory, such asnode_modulesorios/Pods, warm now callsclonefile(2)once on the whole directory through a small helper,stim-clonefile, instead ofcp -Rc, which cloned one file at a time. The helper is compiled on first use withxcrun clanginto$STIM_HOME/helpers, so it needs the Xcode Command Line Tools. On the same APFS volume a directory with 143k files drops from about a minute to seconds. When there are no Command Line Tools, the compile fails, the platform is not macOS, orclonefilerefuses the copy (EXDEVacross volumes,ENOTSUP), warm removes the partial copy and falls back tocp -Rcand thencp -Ras before. What is copied, skipped and swept, the lockfile warnings and the empty stdout are unchanged. One difference: a directory clone keeps the source's symlink modification times, wherecp -Rcstamped the copy time. Fixes #4162. a19e29e2fstim worktree warmremoves copied exclusions (.DerivedData,.pnpm-store,.ideaand the like) with bounded concurrency. The sweep of the copied tree visited directories one at a time; it now reads up to 32 directories at once. On success the set of removed entries is unchanged; after a failure, the first error is thrown once the in-flight directories settle, so up to 31 of them may still finish their removals. Fixes #4163. f71bcbb34stim worktree warmskips Pods files that do not contain the source checkout's path before it runs the relocation regex. On a carriedios/Podsof 19,707 files, detecting the source path used about 10 s of CPU because every file was decoded to a string and matched against a Unicode boundary regex. Stim now checks the file's bytes for the UTF-8 form of the source root first and runs the regex only on files that contain it, measured at about 2 s. Which files are rewritten, what they are rewritten to and when the leftover check refuses are unchanged. Fixes #4168. 57ce656f5stim worktree removeno longer waits fornode_modules,ios/Podsand build outputs to be deleted. Before git removes the worktree, Stim renames its ignored directories into$STIM_HOME/trash/remove-*, holding an ownership claim and writing atrash.json(worktree path and time) first, so git removes a light tree. The CLI then deletes the entry in a detached, low-priorityrmrecorded as the claim's child and prints atrashphase line on stderr (deleting <n> ignored directories in the background from <dir>); disk is freed when thatrmfinishes. Through the programmatic API (removeWorktree), the entry is deleted before the call resolves, because a bounded report stops its children when it settles. Whengit worktree removefails, the directories are moved back; any that cannot be moved back (the place was taken) stay in the entry, and the error names them and the entry. Nothing is renamed on Windows, when$STIM_HOMEis on another filesystem than the worktree, or for a directory that does not rename; git deletes those as before.stim worktree removestdout stays empty. Fixes #4164. (stim,@stim-cli/core) 81d5d8284stim gcreports the worktree trash and deletes only what is safe to delete.gclists each directory under$STIM_HOME/trashunder "Worktree trash" with its size ("deleting" while a removal holds it) and the worktree it came from; the--jsonpayload gainstrash: { root, entries: [{ path, worktree, removedAt, bytes, willDelete, kept }] }(nullwhen the trash is empty or under any--cachescope), and--deleterecords results under thetrashcategory.gc --deletedeletes an entry only when it is aremove-*directory with a validtrash.json, its ownership claim is free, and the worktree path in the manifest is gone. It keeps an entry while that worktree path still exists or cannot be shown absent (anylstaterror other thanENOENTorENOTDIR), which is the state after a removal killed between the rename and git, or after a failed move-back. Without that checkgc --deletewould delete dependency trees from a worktree the user still has. To finish such a removal, runstim worktree removeon that worktree again;gcthen deletes the entry.gcnever moves directories back and keeps any other directory undertrash. Fixes #4194. (stim,@stim-cli/core) 81d5d8284, 837cc6437stim gcstops listing a removed workspace's build outputs as kept. Afterstim worktree removedeletedworkspaces/<id>, the size recorded for it inmaintenance/state.jsonstayed until the next size check.gccounted it toward theworkspace-outputscap and printedkept <home>/workspaces/<id>: workspace outputs cannot be resolvedandblocked: workspace-outputs ... eligible targets cannot reach the target. A recorded size is now dropped before planning whenworkspaces/exists and the recorded directory, a direct child of it, is absent (ENOENTorENOTDIR). Ifworkspaces/is missing or the check fails any other way, the record stays and is still reported as kept. Fixes #4072. 31ab28706- A
STIM_CLAIM_UNAVAILABLErefusal fromstim gcnames the child and the real retry command. Under heavy load, a plaingcdry run could print a remedy that began withundefinedand suggestedstim gc --idle <duration>. The report-child refusal and the 35 second report-cleanup expiry carried no remedy, andgcsuggested--idlefor every run that was not--delete, although--idleshuts down idle owned devices. The report-child refusal now names the child's pid, with aps -p <pid>command on macOS and Linux, and asks you to stop it and its descendants if they are still running; the expiry asks you to check that the child processes the report started and their descendants have stopped. Both give the retry command rebuilt from the options given (--delete,--worktrees,--older-than,--cache,--idle,--json). ASTIM_CLAIM_UNAVAILABLEerror with no remedy, from any command, now asks you to check that no process from the failed command is still running before the retry, instead of printingundefined. The child is still left untouched and the command still exits 1. Fixes #4155. (stim,@stim-cli/core) 791a9ca66 - Claim cleanup waits for an exiting process group before it refuses, scaled by host load. On a heavily loaded host,
stim startrefused withSTIM_CLAIM_UNAVAILABLE("Could not confirm cleanup of supervisor pid N") as soon as a supervisor had exited with a helper, such as Expo'sxcrun simctl help, still in its process group, and that error hidSTIM_SUPERVISOR_EXITEDand blocked the retry onEADDRINUSE. Report children (gcand other reports) had one second after SIGKILL to close. Start now polls the supervisor's process group without signalling it for 5 s times the host's load per CPU (at least 5 s, at most 60 s). A report child is still sent SIGKILL, then gets two waits on the same scale, at most 15 s each, one to close and one for its process group, and its close is judged only after one more I/O turn, so a stalled event loop no longer reads as "did not close within the cleanup budget". When cleanup still cannot be confirmed after a start refusal, the error message starts with the refusal's message and its evidence lines, and the code staysSTIM_CLAIM_UNAVAILABLE. A descendant that never exits now takes the full wait before it is refused, instead of 0 to 1 s. Fixes #4093. 0d07f076b stim gclists and deletes SwiftPM'sTemporaryDirectory.*leftovers inTMPDIR.swift buildandxcodebuildleave directories namedTemporaryDirectory.plus six characters inTMPDIR(six per build), whichgcdid not recognize. They now appear under "TMPDIR leftovers" with kindswiftpmin the--jsonpayload.gc --deleteremoves one only when nothing in it changed for 24 hours, keeps one that cannot be read, and keeps all of them while aswift,swift-build,swift-frontend,swift-package,swift-run,swift-testorxcodebuildprocess runs or the process list cannot be read. Automatic maintenance at disk pressure level 2 deletes them under the same checks. Stim's own SwiftPM macOS builds (stim macos, local and on a build worker) keep runningswiftwith the ambientTMPDIR, where SwiftPM keeps the cross-process locks for its shared caches, so their temp directories land there andgccleans them. Fixes #4157; #4186 was a regression from an earlier commit in this release, also fixed here. 68ebb740a, 0765f8743- Expo apps with a dynamic config skip a second app-config evaluation, and Stim's own Expo children no longer start Expo's telemetry flusher. The remote-cache provider lookup (
buildCacheProvider) read the app config with a secondexpo config --jsonchild after the fingerprint had evaluated it. It now reads the fingerprint'sexpoConfigsource from the same pre-mutation lookup, and spawnsexpo config --jsononly when that source is missing or, under theeasprovider, has noowner. Stim's ownexpo config,prebuild,startandexport:embedchildren now run withEXPO_NO_TELEMETRY=1unless the variable is already set, which drops Expo's detached telemetry process from each call. 5f327a5a4 - Memory-pressure culprits are read off the boot path. Under
warningorcriticalhost memory pressure,stim iosandstim androidran/usr/bin/top -l 1synchronously before preparing the device, which blocked fingerprinting, the boot and the boot monitor for 3 to 4 s per call (a failing iOS run made two). The read is now asynchronous and shared for 30 seconds per process. Boot prints the memory-pressure message at once and the line naming the processes as a secondmemoryphase line whentopfinishes, and prints nothing extra if none is abnormal ortopfails; that second line is lost if the process exits first. Simulator boot-wait errors,androidBootRemedyand the iOS failure advice still wait for the read to build their text. Fixes #4167. 1f6568c9c stim logs --followreads a replacement log file from its first record. The follower used to decide a log file had been rotated only when its size fell below the saved byte offset. A newclient.ndjsonalready as large as the old offset was treated as an append: the follower resumed in the middle of the new file's first record, dropped that partial record, and never read the unread tail of the rotated.1file. It now remembers the device and inode of each followed file. A different identity means the file was replaced: the follower reads the rest of<name>.1(only if.1has the remembered identity), then the replacement from byte 0. A replacement made between the initial backfill and the start of the follow is also recognized, because the backfill view now reports the identity of each file it measured. The same follower serves the server's logs subscription. Fixes #3899. (stim,@stim-cli/core) 7bd2e8e57stim logs --followfinds a hosted slot placed just as it starts. Node'sfs.watchon macOS can drop an event for a change made just afterwatch()returns (nodejs/node#52601), so a hosted placement written in that moment was found only on the next 10 second refresh. Once the state watcher is armed, the follower now rechecks hosted placements after 100 ms. Fixes #4200. a3a6f7106- The local stim-server socket works under a long Stim home. A Unix socket path is limited to 103 UTF-8 bytes on macOS and 107 on Linux, and a hosted session's private home could make
$STIM_HOME/server/local.socklonger than that, so Stim Desktop could not connect to its server. When the path is too long, the socket now lives at/tmp/stim-sockets-<uid>/<sha256 of the socket path>.sockinstead, so distinct homes keep distinct sockets; shorter paths are unchanged. Stim Desktop finds the moved socket from desktop-v0.2.5. Fixes #4083. (@stim-cli/core,@stim-cli/serverREADME) d2ec2c4f3 - Human-mode
stim logsstays linear on a very long line.errorDiagnosticsscanned each record forfile:line:collocations with an unanchored global regex whose cost grew with the square of the longest unbroken run of characters, so one 1.5 MB line took over 14 minutes where--jsontook 2 seconds. The scan now starts only at the beginning of a run, with the same matches.launchErrorPreviewalso no longer treats a line over 2048 characters as a stack frame: such a line prints as an ordinary line with its full text and ends the stack block above it. Only a frame-shaped line over 2 KiB renders differently. Fixes #4129. 4db89561b - An owned iOS simulator boot now waits for SpringBoard to finish starting, within the ten-minute boot deadline. In 1.23.1 the wait was only part of the mute step: after
simctl bootstatus -breturned, Stim polledcom.apple.springboard.finishedstartupfor up to 30 seconds before setting the volume, and the boot itself ended before SpringBoard was up.simctl launchblocks until SpringBoard has started, so on a loaded host the 60 second launch timeout also paid for SpringBoard's startup andstim ioscould fail withSTIM_LAUNCH_FAILEDwhile the app started on its own later. Now the boot polls the same state afterbootstatusunder the existing 600 second boot deadline, reportswaiting for SpringBoardin its progress lines, and the mute reuses that result with no wait of its own. The time shows up in thebooted (Xs)step instead of the launch. If SpringBoard has not started at the deadline, the boot fails with the usualdid not finish booting within 600smessage and memory diagnostics; before, the failure was a later launch timeout. If the state cannot be read (an error other than a timeout, or unexpected output), the boot completes and the mute prints its existing could-not-mute warning, as in 1.23.1. Hosted iOS simulator boots on a device-host worker go through the same boot function and get the wait.stim guide errorsandwebsite/docs/owned-devices.mddescribe the SpringBoard wait. Refs #4061. 81c303433 stim android --planhonors an explicit--system-imageon a React Native or Expo project. The plan keyed its build on the owned emulator's recorded system image even when--system-imagenamed a different one, so it could report a cache key and hit for the old ABI where the run builds for another, or report a plan where the run refuses. The plan now applies the run's rule to the flag: an owned emulator whose boot never finished is treated as replaced and the key comes from the requested image; one that has booted returnsSTIM_BAD_ARGwith the run's message and remedy (stim worktree removeorstim gc --delete, or--slot <name>), for release variants too. Withandroid.remoteset toauto, the refusal applies when placement resolves to this machine and not when a remote machine is chosen.android.systemImagealone still leaves an existing emulator in place, so the plan keys on the recorded image for the setting. Fixes #3838. 608a0650d- Finding a workspace's own Android emulator no longer pays one timeout per foreign emulator. The resolver asked every running emulator for its AVD name (
adb -s <serial> emu avd name, 10 second cap) one after another, so an emulator belonging to another workspace and stuck cost up to a full timeout in each place a run resolves its AVD; one warmstim androidmeasured 4.8 s + 10 s + 10 s. It now reads the serial the workspace recorded first (the record's console port, the serial a run already holds, or thedeviceIdthatstim reloadhas) and asks the others only when that serial does not run the AVD. Within each group (healthy, then unhealthy emulators) the reads run concurrently and the pending ones are cancelled once one reports the requested AVD, so several stuck emulators cost one timeout together. The ownership check on the AVD name runs before any serial is read and the recorded serial must still report the requested name. Fixes #4166. 753cda2fd stim androidon Windows refuses an over-long project root before it starts Metro or prepares the emulator. TheSTIM_PATH_TOO_LONGcheck ran only inside the Gradle build, after Stim had created or booted the emulator, and a device failure such as a full disk hid it. The run plan now applies the same check, with the same code, message and remedy, once the target and build are known. It applies to a local build for an emulator or a--remote <backend>target, with the ABI the build would use. It is skipped for a physical or hosted device, an EAS profile, and any run with a paired build machine (an explicit--remote-build <machine>, orautowith machines paired), where the check in the build still applies. Because it does not wait for a cache lookup, an over-long root on an emulator run is now refused even when a cached build would have installed.stim doctor --platform android,stim guide errorsandwebsite/docs/requirements.mdsay so. Fixes #3671. d9e5bd35astim worktree warmreports a git timeout as a timeout, and on Windows a contained child's time limit counts only the child. On Windows, warm's first git probe ran through the PowerShell report-job wrapper, whose startup could use the whole 10 second git budget on a fresh runner, and the timeout read asNot a git repository.On every platform, warm's path probes now fail withCommand timed out after 10000ms: git ...; a real non-repository still reportsNot a git repository.For bounded calls, the report-job entry now writes a marker file in the OS temp directory (stim-report-<pid>-<uuid>.startedand.closed, removed afterwards) when the child starts and closes, and the caller'stimeoutMsruns only between them. PowerShell's startup and exit each get their own 30 second limit, and exceeding either fails withThe Windows report job did not start within 30000ms: ...or... did not exit within 30000ms: ..., codeETIMEDOUT. Marker failures never fail the call; a missing marker leaves the 30 second bound in force. The programmatic API guide andwebsite/docs/programmatic-api.mdstate the rule. Fixes #4102. 325280205- On Windows, every contained child call is about 2 seconds faster. The report-job script ended with
[System.Environment]::Exit, which took about 2.5 seconds onwindows-latest; it now ends withexit $child.ExitCode, which sets the same exit code (checked for 0, 3, 255, 256, -1, 1073741823 and -1073741819) and keeps the Job Object containment. Each containedgitcall instim worktree warmwent from 2.8-3.0 s to 0.5-0.8 s, and a whole warm of the bare fixture from 44-45 s to 19-21 s. Fixes #4103. fc12df8eb stim-serverkeeps serving other connections while a run, stop, warm, remove or gc waits for most locks held by anotherstimprocess. The server runsrun,build,stop,worktree warm|removeandgcin its own process throughcreateStim. Many writes on those paths (device records, the created-devices ledger, the project registry, workspace state, hosted placement records, the EAS session ledger, prebuild fingerprints, device-capacity stats and error-diagnostic snapshots) waited for a contended lock withAtomics.wait, which stopped the server's whole event loop for up to the 12 second lock wait, so every other connection stalled. These waits now run on a timer. The claim protocol, reentrant nesting, bounded wait andSTIM_LOCK_TIMEOUTerror are unchanged. The CLI itself, a short-lived process, behaves the same; the supervisor, collector, web and macOS supervisors, maintenance and the offload worker keep the synchronous wait on purpose. The build and warm progress recorders, the run-stats recorder and the physical-device lease lock still wait synchronously, among others. Closes #3532. 3774dd83e, 7dd7cde8d, f71fe11e0
Docs
- The guide and website now say the worker writes
NODE_BINARYon every offloaded iOS build.stim guide settingsandremote-machines.mddescribed the.xcode.env.localwrite as part of the pinned-Node behavior. The worker writes the firstnodeon itsPATH(the pinned Node when it put one first) asNODE_BINARY, pinned or not, replacing anyexport NODE_BINARY=line. No behavior change. Fixes #4085. aa413d304 - The
stim-serverREADME and the remote-machines page state that a local process on the host can forgeX-Forwarded-Forto lock one named peer, or the shared local bucket, out for the lockout window. Tailscale rewrites the header to the real tailnet source, so remote clients cannot forge it; the header still authenticates nothing and reads nothing, and the exposure needs local code execution on the host. Documentation only; also touchesdocs/specs/2026-10-13-pluggable-transports-design.md. (@stim-cli/serverREADME) f00e07be4
Migration notes
- EAS Simulator sessions now end after 30 minutes by default. Before 1.24.0 Stim passed no maximum duration, so a session ran until the EAS plan's own limit. For longer sessions, raise the cap up to your plan's limit with
stim settings set remote.easMaxDurationMinutes <minutes> --scope machine. A run after the cap creates a new session. - Update the client Mac and the build or hosting Mac together. The hosted iOS Expo dev-client fix needs 1.24.0 on both Macs: a 1.24.0 client with an older host, or the reverse, keeps the old random bridge port and the JavaScript still does not load. Offloaded builds already require the same Stim build on both Macs. Update a worker's service with
stim-server service update --release 1.24.0. - Gitignored fingerprint inputs now go to build workers. An offloaded React Native or Expo build uploads the gitignored files its fingerprint reads, including Expo env files such as
.env.local, to the paired worker. Pair only workers you trust with those files. - Some iOS build-cache keys change once. Stim's fingerprint now ignores
*.xcodeproj/xcuserdataand, while a.gitignorepattern matches them,ios/main.jsbundleandios/assets. A project with those paths misses its existing build-cache entries on the first build after upgrading. Macs still on 1.23.x that share a remote cache compute the old keys for these projects, and Expo CLI builds that use@stim-cli/expo-build-cachestop sharing those entries with Stim unless the paths are listed in.fingerprintignore. - A build worker reinstalls a pnpm repository once. The worker no longer filters the pnpm install to the app, so a repository installed with the filter under 1.23.x reinstalls on its first 1.24.0 build.
StimReloadResult.appIdcan benull. The programmatic API'sreload()result now typesappIdasstring | null, because a reload through Metro of an app Stim did not launch has no app id. TypeScript callers that treat it as a string need a null check.- Hosted log records written before 1.24.0 drop out of
logs --errorsand thestatuserror counts. This includes hosted Android crash lines, which were never stampednative_crash. The records are not rewritten and remain under--source deviceand--source all.
QA
The maintainer authorized a stable release without the pre-tag QA gate on 2026-10-21 ("Lets cut a quick release"), as for 1.22.0, 1.23.0 and 1.23.1. This deviates from RELEASE.md section 3, which has no waiver for a stable version; the expedited RC lane is for prereleases only and was not used. The waiver covers only section 3. No section 3 QA round ran on any commit since v1.23.1, so no change in this release has been through QA, including the hosted iOS Expo dev-client Metro port, the EAS session cap, the worker's install and fingerprint changes, the worktree trash and the iOS boot's SpringBoard wait.
Omitted before the tag, for this candidate: all eight rows that node scripts/release-qa-matrix.mjs v1.23.1 requires (282 changed paths with the version commit, 140 mapped to a row, 141 with no native effect, 1 unclassified): loop (expo and bare, iOS and Android), logs (iOS, Android), caches (iOS, Android), real-repository, remote-provider, release-build (iOS, Android), android-flavor and launch-evidence (iOS, Android), plus the native suites test/e2e/native/run-native-e2e.mjs and test/e2e/native/run-cache-e2e.mjs. The one unclassified path, vitest.global-tmpdir.ts, is unit-test configuration that the mapping does not list yet (#4203); it would on its own require the full matrix, which was omitted in full under the waiver. Not run: test:compat, which needs a connected iPhone.
Run before the version commit, on the bumped candidate: format:check, lint, build, typecheck, knip, release:prep --check (12 packages at 1.24.0, 23 workspace: ranges), test:runtime, CLI --version (1.24.0), --help and guide agent, licenses:check and format:check in apps/mobile, and pnpm tarball inspection of the twelve packages (README and LICENSE in each, version 1.24.0, internal dependencies on ^1.24.0 or 1.24.0, no workspace: range, file counts equal to 1.23.1's except stim, 209 against 208, which adds dist/stim-clonefile.c). pnpm test ran 10316 tests on a Mac at load average 40 to 130: 10286 passed, 28 skipped, 2 failed in 2 files, one of them a timeout. Rerun alone, packages/server/__tests__/device-host.test.ts and packages/stim-cli/src/__tests__/warm-claim.test.ts passed (196 of 196). The local unit result is therefore not a pass; the full unit suite is left to exact-commit CI. test:e2e ran 118 tests: 117 passed and 1 failed. test/e2e/storage-volumes.e2e.js fails on macOS, also when run alone, because it forces the cp -Rc fallback while the stim-clonefile helper from a19e29e2f clones first; the test does not intercept the helper (#4205). CI runs test:e2e only on Linux, where that test is skipped, so no gate covers it. Those runs were on the candidate built from f71fe11e0. After main gained d2ec2c4f3, a3a6f7106, dbd1266c0 and 6771f8539 and the candidate was updated to include them, these were rerun on it and passed: format:check, lint, build, typecheck, knip, test:runtime, release:prep --check, CLI --version, --help and guide agent, the tarball inspection, and the unit test files those four commits touch (1139 passed, 1 skipped). The full pnpm test and test:e2e were not rerun on it.
Run after the version commit landed: exact-commit CI on main (Node 22 and Node 24 jobs and published runtime (node 22.12.0)), and the Release workflow's own gates. The full Windows unit job of a dispatched CI run on the candidate is recorded, not gated, per #3878.
1.23.1
Stim Desktop and the Stim phone app ship through their own workflows; this release covers the twelve npm packages (@stim-cli/core, @stim-cli/cache, @stim-cli/cache-s3, @stim-cli/agent-driver, @stim-cli/agent-driver-agent-device, @stim-cli/metro, @stim-cli/expo-build-cache, stim, @stim-cli/ci, @stim-cli/transport, @stim-cli/transport-tailscale and @stim-cli/server). No package publishes for the first time in this release. Commits that only changed those apps, tests, CI or internal structure are not listed.
Fixes
- An offloaded build runs the project's own Node code on the Node the project pins. When the worker's own
nodedoes not fit the Node that.nvmrc,.node-version,.tool-versionsorpackage.json'svolta.nodepins, in the app's directory or the nearest one above it in the checkout, the build worker puts an installed Node that does first onPATHbefore the dependency install, prebuild, pods and xcodebuild. It logs the Node it builds on on thedepsline. On every offloaded iOS build, pinned or not, it writes the firstnodeon itsPATHasNODE_BINARYin the project's.xcode.env.local, which React Native's Xcode script phases read, replacing anyexport NODE_BINARY=line already there. Before, those steps ran on the worker's ownnode, so a project pinning another major could fail to install or build on the worker. A warm Gradle daemon keeps running React Native's Node steps on the Node it started with (#4081). The worker's dependency marker names the pinned Node's major, so a change of major reinstalls the dependencies and an install made under another major is not ranked as reusable. When no installed Node fits the pin, the steps run on the worker'snodeand adeps-failedorprebuild-failedreason names the pin and that Node's version. Fixes #4077. (stim,@stim-cli/server,@stim-cli/core) 3b5309620 - Pinned Node discovery finds linked Homebrew
nodeandnode@NNinstalls, and falls back within the pinned major. This applies to Metro's Node and to the build worker. When no install has the exact version the pin names, Stim takes the newest installed Node of the pinned major that is not older than the pin, such as Homebrew'snode@22at 22.23.2 for a22.22.0pin, and keeps anodeonPATHthat meets the same rule. The Metro log, the worker log andstim doctorsay when a fallback was used, andstim doctor --jsonaddsmatch("exact"or"major") totoolchain.projectNode. (stim,@stim-cli/core) 3b5309620 - An owned iOS simulator stays muted after boot. SpringBoard sets its default volume while it starts, a few seconds after
simctl bootstatus -breturns on a warm boot, so the mute Stim applied and confirmed right after boot was undone and the simulator played sound at volume 60. Stim now waits up to 30 seconds for SpringBoard'scom.apple.springboard.finishedstartupstate before it sets and confirms volume zero. When that state cannot be read or SpringBoard does not finish in time, startup prints the existing could-not-mute warning instead ofsimulator muted. Fixes #4075. f349af1da
QA
The maintainer authorized a stable release without the pre-tag QA gate on 2026-10-20, as for 1.22.0 and 1.23.0. This deviates from RELEASE.md section 3, which has no waiver for a stable version; the expedited RC lane is for prereleases only and was not used. The waiver covers only section 3.
Earlier on 2026-10-20, a full section 3 QA round ran against main at c132bb118, with v1.21.0 as the regression baseline: all eight rows of node scripts/release-qa-matrix.mjs v1.21.0, on Expo and bare fixtures, Word-Darts and tlon-apps, with janics-mac-mini as the remote worker. It found no regression from v1.21.0. It filed #4075 (the iOS simulator mute did not hold, a 1.22.0 claim) and pre-existing issues #4069 (HIGH: a hosted iOS Debug Expo dev-client app never loads JS, still open and not addressed by this release), #4070, #4071, #4072, #4073 and #4074. That round did not cover either change in this release: both fixes above landed after c132bb118, and the mute fix for #4075 was not exercised by it.
Omitted before the tag, for this candidate: all eight rows that node scripts/release-qa-matrix.mjs v1.23.0 requires (41 changed paths with the version commit, 11 mapped to a row, 30 with no native effect, 0 unclassified): loop (expo and bare, iOS and Android), logs (iOS, Android), caches (iOS, Android), real-repository, remote-provider, release-build (iOS, Android), android-flavor and launch-evidence (iOS, Android), plus the native suites test/e2e/native/run-native-e2e.mjs and test/e2e/native/run-cache-e2e.mjs. None of the items above was exercised by hand on this candidate before the tag, including the worker's pinned Node and the SpringBoard wait. Not run: test:compat, which needs a connected iPhone.
Run before the version commit, on the bumped candidate: format:check, lint, build, typecheck, knip, release:prep --check (12 packages at 1.23.1, 23 workspace: ranges), test:runtime, test:e2e (116 of 116), CLI --version (1.23.1), --help and guide agent, licenses:check and format:check in apps/mobile, and pnpm tarball inspection of the twelve packages (README and LICENSE in each, version 1.23.1, internal dependencies on ^1.23.1 or 1.23.1, no workspace: range, file counts equal to 1.23.0's). pnpm test ran 10173 tests on a Mac at load average 24 to 30: 10144 passed, 27 skipped, 2 failed in 2 files. Rerun alone, packages/stim-cli/src/__tests__/hosted-ios-client.test.ts passed (81 of 81); scripts/agent-benchmark/macos-processes.test.mjs fails at its sandbox-exec call on this Mac, as in 1.21.0 to 1.23.0. The local unit result is therefore not a pass; the full unit suite is left to exact-commit CI.
Run after the version commit landed: exact-commit CI on main (Node 22 and Node 24 jobs and published runtime (node 22.12.0)), and the Release workflow's own gates. The full Windows unit job of a dispatched CI run on the candidate is recorded, not gated, per #3878.
1.23.0
Stim Desktop and the Stim phone app ship through their own workflows; this release covers the twelve npm packages (@stim-cli/core, @stim-cli/cache, @stim-cli/cache-s3, @stim-cli/agent-driver, @stim-cli/agent-driver-agent-device, @stim-cli/metro, @stim-cli/expo-build-cache, stim, @stim-cli/ci, @stim-cli/transport, @stim-cli/transport-tailscale and @stim-cli/server). No package publishes for the first time in this release. Commits that only changed those apps, tests, CI or internal structure are not listed.
New
- Private Desktop stim-server method
desktop.settings.schema(featuredesktop-settings-schema). It returns the server's registry-derived settings JSON Schema, takes no parameters, and is available only to the Desktop connection that proves the bundled server's private bootstrap key for the same Stim home; phones, other local clients and remote clients are refused. Stim Desktop's Settings editor reads its field definitions from it instead of from schema files of an external CLI installation. (@stim-cli/server,@stim-cli/core) 8f9ae81a4
Fixes
- A fingerprint change no longer deletes a gitignored Expo native directory and its Pods. When
ios/orandroid/was not generated from the current fingerprint, Stim ranexpo prebuild --clean, which deleted the directory, includingios/Pods, and forced a fullpod install. It now updates the directory in place withexpo prebuild --no-cleanon Expo SDK 57 and later, whose CLI deletes native folders by default, and with plainexpo prebuildbefore SDK 57, whose CLI keeps them and rejects--no-clean. On iOS it then runspod installagainst the existingios/Pods. The build progress line readsnot generated from this fingerprint -> updated in place. A build offloaded to another machine still regenerates that machine's copy with--clean. When the in-place prebuild fails,STIM_PREBUILD_FAILEDcarries a remedy to delete the directory and run again;stim guide errors,stim guide lifecycleand the build-caches page list the changes that need a fresh directory (a removed config plugin, an Expo SDK or React Native upgrade, an app rename, a plugin that is not idempotent) and--no-build-cachefor a build already cached under the current fingerprint. 0a0e046d6
Migration notes
- An in-place prebuild does not undo what earlier config wrote into a gitignored
ios/orandroid/. After removing a config plugin or upgrading the Expo SDK or React Native, delete that directory before the nextstim iosorstim android.
QA
The maintainer authorized a stable release without the pre-tag QA gate on 2026-10-19 ("can u do a quick no qa reg release"), as for 1.22.0. This deviates from RELEASE.md section 3, which has no waiver for a stable version; the expedited RC lane is for prereleases only and was not used. The waiver covers only section 3.
Omitted before the tag: all eight rows that node scripts/release-qa-matrix.mjs v1.22.0 requires (40 changed paths, 8 mapped to a row, 32 with no native effect, 0 unclassified): loop (expo and bare, iOS and Android), logs (iOS, Android), caches (iOS, Android), real-repository, remote-provider, release-build (iOS, Android), android-flavor and launch-evidence (iOS, Android), plus the native suites test/e2e/native/run-native-e2e.mjs and test/e2e/native/run-cache-e2e.mjs. None of the items above was exercised by hand on this candidate, including the in-place prebuild and the desktop.settings.schema method. Not run: test:compat, which needs a connected iPhone.
Run before the version commit, on the bumped candidate: format:check, lint, build, typecheck, knip, release:prep --check (12 packages at 1.23.0, 23 workspace: ranges), test:runtime, test:e2e (116 of 116), CLI --version (1.23.0), --help and guide agent, licenses:check and format:check in apps/mobile, and pnpm tarball inspection of the twelve packages (README and LICENSE in each, version 1.23.0, internal dependencies on ^1.23.0 or 1.23.0, no workspace: range). pnpm test ran 10165 tests on a Mac at load average 33 to 40: 10133 passed, 27 skipped, 5 failed in 5 files, four of them timeouts. Rerun alone, packages/stim-cli/src/__tests__/hosted-log-discovery.test.ts, packages/stim-cli/src/__tests__/metro.test.ts and packages/stim-cli/src/__tests__/ports.test.ts passed; scripts/agent-benchmark/metro-listeners.test.mjs timed out again, and scripts/agent-benchmark/macos-processes.test.mjs fails at its sandbox-exec call on this Mac, as in 1.21.0 and 1.22.0. The local unit result is therefore not a pass; the full unit suite is left to exact-commit CI.
Run after the version commit landed: exact-commit CI on main (Node 22 and Node 24 jobs and published runtime (node 22.12.0)), and the Release workflow's own gates. The full Windows unit job of a dispatched CI run on the candidate is recorded, not gated, per #3878.
1.22.0
Stim Desktop and the Stim phone app ship through their own workflows; this release covers the twelve npm packages (@stim-cli/core, @stim-cli/cache, @stim-cli/cache-s3, @stim-cli/agent-driver, @stim-cli/agent-driver-agent-device, @stim-cli/metro, @stim-cli/expo-build-cache, stim, @stim-cli/ci, @stim-cli/transport, @stim-cli/transport-tailscale and @stim-cli/server). No package publishes for the first time in this release. Commits that only changed those apps, tests, CI or internal structure are not listed.
Removed (breaking)
- Custom Expo
buildCacheProviderplugin modules are no longer supported. A project whoseexpo.buildCacheProvidernames a custom module now gets an unsupported-provider note ("Custom Expo buildCacheProvider plugins are not supported. Use EAS or a Stim cache provider.") in--planoutput and iniosandandroidbuild progress (provider not usable: ...), and falls back as if no provider were configured; the module is never loaded or run.expo.buildCacheProvider: "eas"(EAS CLI caching), the local Expo cache adapter and@stim-cli/cacheproviders, including S3, are unchanged. If you use a custom plugin, switch to EAS or a Stim cache provider. (stim) 94bb19775
New
- Completed builds on paired workers are reused instead of rebuilt. After a local and shared-cache miss,
stimasks the paired worker for a completed build of the same inputs before placing a build, including when the worker is busy or the caller is idle. A hit skips source upload and compilation; a missing or corrupt archive follows normal placement, and older workers keep their normal build path. Each kind is negotiated by its own server feature and uses the existing authenticated, repository-scoped file cache. (stim,@stim-cli/server,@stim-cli/core)- Native Gradle: the worker caches APKs under its own complete inputs (worker toolchain, signing files and environment values) when the build transfers a complete
android.artifactInputsdeclaration, and the caller can fetch a completed APK with featurenative-gradle-peer-cache. Policy flows throughnative-gradle-artifact-cache. The returned fingerprint staysnull, and worker APKs never enter the caller-key cache. 0bac453be, 4fcf220e9 - React Native and Expo development builds: exact iOS Simulator and Android archive lookup with feature
react-native-peer-cache, matching the post-mutation native fingerprint, normalized build choices and a compatible toolchain. Release and physical-device builds keep their existing paths. ac95a793a - SwiftPM macOS apps: reuse of a worker's completed app with feature
swiftpm-peer-cache, for apps that declare complete, repeatable inputs throughmacos.artifactInputs. d87f39cab
- Native Gradle: the worker caches APKs under its own complete inputs (worker toolchain, signing files and environment values) when the build transfers a complete
- Completed SwiftPM macOS apps are reused across worktrees.
macos.artifactInputs(project setting, object) withcomplete: truedeclares pinned inputs that produce a relocatable app, withignored,outputs,localFilesandenvironmentfor exact ignored, generated, private or environment inputs. A hit comes from the local build cache or anoptimizations.buildCache/cache.providerfilesprovider, is verified, and is re-signed with the receiving workspace's identifier and name before placement or build-slot acquisition. Without the declaration, builds compile as before.stim macos --planreportscacheHitfor a verified local completed artifact. Seestim guide macosfor what the declaration must cover. bb4dfc43a - Build placement ranks workers by reusable dependency installs. For React Native and Expo, a worker whose existing install matches the requested project, lockfile and package-manager arguments (modules and install marker present) ranks before load. Checkout and build-folder presence, and the unverified hints of older workers, no longer affect ranking. Clients send
dependencies: { project, packageName }only to servers with the new featurebuild-dependency-cache. Exact completed-artifact hits still come first. (stim,@stim-cli/server,@stim-cli/core) 439c20847 - Owned simulators and emulators are muted each time Stim boots them. After a successful boot, Stim sets the volume to 0 and reads it back:
xcrun devicectl device settings audio --device <udid> --volume 0for iOS simulators, and for Android emulators the media stream only, through a 1.4 KB helper shipped in the CLI (shim/android-audio.jar) that Stim pushes to/data/local/tmp, runs withapp_process, removes, and confirms withcmd media_session volume --stream 3 --get; this works on Android 14, whose shell ignores the plain volume setter, and needs no host JDK. Stim prints adevicephase line when the mute is confirmed and a warning, then continues, when it is not. A device that is already running is not touched, so sound turned on with Stim Desktop's per-device toggle lasts until that device next boots. Hosted (device-host) boots are unchanged. 2459e5df3, 289d8701d stim-server service update --no-waitandstim-server setup --no-wait. Service updates wait for offloaded builds and hosted sessions to finish by default.--no-waitrestarts with that work active: shutdown interrupts it, and shutdown verification, health checks and rollback still apply. Setup applies the flag to the service update it runs, and its remedy for a separate update carries it. (@stim-cli/server) c180eb0cf- In-process APIs on
createStim. Each accepts anAbortSignaland drains its owned children and claims on cancellation, reportingSTIM_CLAIM_UNAVAILABLEwith recovery paths when cleanup cannot be verified:plan({ platform: 'ios' | 'android' | 'macos', slot?, signal? })returns the next configured native build as--plan --jsonreports it, without building or launching. (stim,@stim-cli/cache,@stim-cli/cache-s3) 51c5cd60bstart({ remote?, resetCache?, waitSeconds?, signal?, beforeStart? })starts or reuses the managed dev server and returns flat start facts. Starting and stopping from a child folder resolve the same enclosing app. 243841b81collectGarbage({ cache?, olderThan?, worktrees?, delete?, idleMs?, signal? })runs the CLI's GC selections and actions and returns{ report, exitCode };gc()stays read-only. A deleting or idle sweep takes the home's exclusive maintenance claim and a concurrent one rejects withSTIM_CLAIM_REFUSED. 690f2dc8aremoveWorktree({ target?, force?, signal?, beforeRemove? })returns{ removed, exitCode }with the CLI's removal checks. 28c3ac257warmWorktree({ signal?, beforeWarm? })copies missing ignored state into the linked worktree and returns{ exitCode }, with a 90 minute execution and 35 second cleanup budget. It does not refresh or install dependencies. A failed copy that created a new entry now removes that partial entry after its child drains. b5ff73bf4requestMachineAccess({ ticket?, signal? })requests build and device-host access from the configured remote machines without applying project repairs; a person still approves each capability on the worker. It returns the iOS doctor report. 757f90b40run()exposes the existingremoteselector for native runs and forwards it for macOS, so a macOS request withremote: '<machine>'no longer takes the local launch path. The macOS result'slaunchedtype now includes'unverified'. dc29525ed
- New private Desktop stim-server methods and features. Each is available only to the Desktop connection that proves the bundled server's private bootstrap key (for the same Stim home); phones, other local clients and adopted servers are refused. Each subscription streams
desktop-*-progressanddesktop-*-endedevents;desktop.plan.getreturns the plan in its reply. Each runs the in-process API above, waits for cleanup on cancellation and never replays on reconnect. (@stim-cli/server,@stim-cli/core)desktop.run.subscribe(featuredesktop-run): iOS, Android, macOS and web runs, with a one-hour default deadline and a 1 MiB output limit. c80c8cf89desktop.build.subscribe(desktop-build): artifact-only builds for iOS, Android and macOS. c5393e953desktop.start.subscribe(desktop-start): dev-server startup. 243841b81desktop.plan.getanddesktop.plan.cancel(desktop-plan): next-build reports for a selected existing folder. The registered-workspacebuild.planmethod for phones now also runs in process. 89034c015, e5ca4426cdesktop.gc.subscribe(desktop-gc): GC selections and actions, preserving a failing exit status on partial failure. ac7b6952edesktop.worktree.remove.subscribe(desktop-worktree-remove) anddesktop.worktree.warm.subscribe(desktop-worktree-warm, 90 minute default deadline). 3a4921c14, aa73c5e6fdesktop.machine-access.subscribe(desktop-machine-access):{ cwd, ticket? }. 757f90b40
- Lower idle cost and faster reaction in several waits. Waits that polled now wake on file, process or write events, keeping a slower repair check for a lost event:
stim logs --source agent --followand server log subscriptions follow agent state and claim directories instead of re-reading every 500 ms (repair read every ten seconds). f4b41e242stim-server setupnotices the requesting machine's approval from registry changes (one-second repair). a1cae8426- Windows service startup waits on
supervisor.jsonchanges instead of a TaskScheduler query every 250 ms (one-second repair). e170583bc - Hosted Metro gateway acknowledgements wake from the state file instead of a 100 ms reread (caller repair is one second after the first 500 ms). 1ab1c5442
- Browser launch verification (
stim web) reads the owned browser log by file events, and browser registration wakes from its supervisor by IPC (Windows keeps 250 ms checks). b2c2acb60, 232a123be - Local and hosted macOS launches (
stim macos) wake from the supervisor by IPC. 83cd41dc6 - Binary build-source uploads wake backpressure from write completion instead of a 5 ms check. 4767d52a8
- Managed tunnel, Metro startup rollback and Windows launcher cleanup finish on child exit instead of a 25 ms delay. d1882ea04
@stim-cli/coreoversightwakeAtincludes physical-device lease expiry and unattended EAS-session age, so callers can schedule those attention changes without polling. 7ae982cde- Status retries pending merge verdicts without rescanning unchanged worktrees (37 to 16 git status commands in a real eight-worktree fixture). 6ec3a74ed
Fixes
ios --deviceandandroid --deviceignoreios.remoteandandroid.remote. With either setting atautoor a machine name, a physical-device run refused withSTIM_BAD_ARG. Those settings place owned simulators and emulators, so--deviceno longer reads them.--device --remote localnow works.--devicewith any other--remotestill refuses, and the error now names the value passed. 23bbd7bd4- Build workers recognize an available iPhone by CoreSimulator's device-type identifier instead of its name, so simulators that Stim renamed are found for offers and destination selection; an iPad named "iPhone" stays excluded. (
stim) 1a747b4c3 - S3 file uploads reopen the completed file and retry up to three PUT attempts on
ECONNRESET, with short cancellable delays and conditional writes preserved. (@stim-cli/cache-s3) 6c318763b - The supervisor stays alive and keeps its registrations and hosted gateways when the dev server's shutdown cannot be confirmed, so a later shutdown can retry. Previously a command that survived termination could outlive a supervisor that had cleared its state. 6745ce291
- A successful browser navigation no longer leaves a timeout that delays a standalone caller's natural exit by about one second. 480879f43
stim status --watchon macOS stops without per-workspace delays: it shares one recursive workspace watcher, and watches CoreSimulatordevice.plistfiles instead of each device directory. A real-home probe took 17.8 seconds to exit after SIGINT before and 4.18 seconds after; a 15-device fixture's watcher disposal fell from 1.11 s to 140 ms. 1b5c264c8, e60218eb4- Doctor's setup command, once approved on the worker, prints the instruction to run
stim doctoragain on the requesting machine beforestim-server setupwaits. Ordinary setup and Desktop-generated commands are unchanged. (@stim-cli/server,stim) 6063465bd
Migration notes
- Owned simulators and emulators boot muted. Turn sound on per device in Stim Desktop's options (Sound for simulators, Media sound for emulators); it lasts until that device next boots.
- A project that relied on a custom
expo.buildCacheProvidermodule must move to EAS ("eas") or a@stim-cli/cacheprovider.
QA
The maintainer authorized a stable release without the pre-tag QA gate on 2026-10-18 ("Can u do a no qa release regular release, no rc"). This deviates from RELEASE.md section 3, which has no waiver for a stable version; the expedited RC lane is for prereleases only and was not used. The waiver covers only section 3.
Omitted before the tag: all eight rows that node scripts/release-qa-matrix.mjs v1.21.0 requires (415 changed paths, 210 mapped to a row, 0 unclassified): loop (expo and bare, iOS and Android), logs (iOS, Android), caches (iOS, Android), real-repository, remote-provider, release-build (iOS, Android), android-flavor and launch-evidence (iOS, Android), plus the native suites test/e2e/native/run-native-e2e.mjs and test/e2e/native/run-cache-e2e.mjs. None of the items above was exercised by hand on this candidate, including the muted boot, worker artifact reuse and the custom Expo buildCacheProvider removal. Not run: test:compat, which needs a connected iPhone.
Run before the version commit, on the bumped candidate: format:check, lint, build, typecheck, knip, release:prep --check (12 packages at 1.22.0, 23 workspace: ranges), test:runtime, test:e2e (116 of 116), CLI --version (1.22.0), --help and guide agent, licenses:check and format:check in apps/mobile, and pnpm tarball inspection of the twelve packages (README and LICENSE in each, version 1.22.0, internal dependencies on ^1.22.0 or 1.22.0, no workspace: range). pnpm test ran 10162 tests on a Mac at load average 110 to 190: 10100 passed, 27 skipped, 35 failed in 16 files, almost all timeouts or timing assertions. Rerun alone, at load average 187, packages/server/__tests__/device-host.test.ts, packages/stim-cli/src/__tests__/report-runtime.test.ts and packages/stim-cli/src/__tests__/start-context.test.ts passed and 27 tests in the other 13 files failed again; scripts/agent-benchmark/macos-processes.test.mjs fails at its sandbox-exec call on this Mac, as in 1.21.0. The local unit result is therefore not a pass; the full unit suite is left to exact-commit CI.
Run after the version commit landed: exact-commit CI on main (Node 22 and Node 24 jobs and published runtime (node 22.12.0)), and the Release workflow's own gates. The full Windows unit job of a dispatched CI run on the candidate is recorded, not gated, per #3878.
1.21.0
Stim Desktop and the Stim phone app ship through their own workflows; this release covers the twelve npm packages (@stim-cli/core, @stim-cli/cache, @stim-cli/cache-s3, @stim-cli/agent-driver, @stim-cli/agent-driver-agent-device, @stim-cli/metro, @stim-cli/expo-build-cache, stim, @stim-cli/ci, @stim-cli/transport, @stim-cli/transport-tailscale and @stim-cli/server). @stim-cli/cache-s3, @stim-cli/agent-driver, @stim-cli/agent-driver-agent-device, @stim-cli/transport and @stim-cli/transport-tailscale publish their first release. Commits that only changed those apps, tests or internal structure are not listed.
Breaking: re-pair every device
This minor release breaks pairing: stim-server speaks protocol 2 only, and no pairing, approval or credential from protocol 1 is carried over. Every network connection to stim-server, loopback TCP included, now runs a Noise handshake before any request: a new client spends a single-use pairing token or setup ticket once, and from then on the server recognizes it by its own public key and the client accepts only the server key it pinned. There is no backwards compatibility in either direction: a protocol 2 stim-server refuses an older phone app, Stim Desktop or stim CLI with protocol-unsupported ("Update this app or Stim, then pair again"), and a protocol 2 client cannot talk to stim-server 1.20.2 or older. Stim Desktop and other clients of the same user on the same Mac no longer pair: they connect to the Unix socket $STIM_HOME/server/local.sock (mode 0600), which needs no handshake. (@stim-cli/server, @stim-cli/core, stim) be8cb61ae, 8be0bc834
What this means on disk: server/devices.json and the *-clients.json approval files from 1.20.2 are not read, and the first protocol 2 write replaces them, so going back to 1.20.2 afterwards loses them too. server/secrets.json stores each pairing token's or setup ticket's derived PSK and lookup, never the token, and server/pairing.json is no longer used. The client keeps its credentials for the machines in remote.machines in the same files, build-machines.json and device-host-machines.json, with a new schema; old entries are ignored, so remote builds and hosted devices on those machines stop until the machine approves this one again. Automatic placement falls back to a local build; a build named with --remote-build <machine> fails with STIM_OFFLOAD_REFUSED, and so does automatic placement when remote.buildPoolDisabled disables local builds.
Update every machine that runs stim-server or stim to 1.21.0 first, then re-pair. Only Stim Desktop 0.2.0 or later and the Stim phone app build released with 1.21.0 speak protocol 2: Stim Desktop 0.1.28 and older bundle a protocol 1 stim-server and make protocol 1 pairing codes and setup commands, so for Desktop users the steps below take effect once Stim Desktop 0.2.0 or later is installed. Stim Desktop 0.2.0 also requires stim and stim-server 1.21.0: once Sparkle installs it, a Mac whose stim CLI is still 1.20.2 cannot run stim from Desktop, local use included, until the CLI is updated (npm install --global stim@1.21.0). Update the CLI before or together with Desktop.
-
Each Mac or machine running stim-server. Update the server itself, on that machine. Stim Desktop's bundled server updates with Stim Desktop 0.2.0 or later. A service installed with
stim-server service installupdates withstim-server service update --release 1.21.0run on that machine (npx --yes --package @stim-cli/server@1.21.0 stim-server service update --release 1.21.0without a global install). A protocol 2 client cannot update a protocol 1 machine over the tailnet: remote update needs the machine's pinned server key from a protocol 2 approval. -
The phone app. Install the Stim phone app build released with 1.21.0. It deletes its protocol 1 pairings on first launch. In Stim Desktop 0.2.0 or later, open the Phones page (if it is missing, turn on the Phone app flag under Settings > Advanced > Feature Flags), turn on Serve to phones, choose Pair a Phone… and scan the QR code. Without Desktop, run
stim-server pair --json(npx --yes --package @stim-cli/server@1.21.0 stim-server pair --jsonwithout a global install) and enter its endpoint and token on the phone. The QR code carries the server key's fingerprint, which the phone checks before it pairs; manual entry carries no fingerprint. A phone that meets a server older than protocol 2 shows "Update stim-server on this Mac. It speaks an older protocol than this app.", and a QR code from an older server is refused with "This pairing code is from an older stim-server. Update stim-server on this Mac." -
Stim Desktop. Install Stim Desktop 0.2.0 or later. Nothing to pair on its own Mac: it talks to its bundled server over the Unix socket. If a stim-server 1.20.2 already answers on Desktop's port, either a service from
stim-server service installor the executable set as stim-server executable under Settings > Advanced, Desktop uses it and showsThis stim-server <v> is too old for this Stim Desktop. Update it, or clear the stim-server override in Settings.Update it withstim-server service update --release 1.21.0, or clear that field. Then approve each remote machine again (step 4); Desktop's Add Machine wizard generates the command. -
Remote machines for builds and hosted devices. Update the remote machine to stim-server 1.21.0 first (step 1). Then, on the machine that sends work, run
stim doctor --fix, or use the Add Machine wizard of Stim Desktop 0.2.0 or later. For each machine inremote.machinesit makes a new setup ticket and prints the command a person runs on that machine to approve it, in npx form with thisstim's own version:STIM_SETUP_TICKET=<ticket> npx --yes --package @stim-cli/server@1.21.0 stim-server setup --expires <ISO-time> --build --device-hostIt drops
--device-hostfor a machine that is not a Mac, and on Windows the variable is set the PowerShell way ($env:STIM_SETUP_TICKET=...), as Stim Desktop's Add Machine command does. Setup updates an older service installed withstim-server service installin place, and refuses an older server it does not manage, such as one an older Stim Desktop runs, with an update remedy.stim doctorreports a machine asnot-askedbefore any--fix, aspending, with the command printed again, after--fixuntil a person runs setup, and tells you when the machine's stim-server is older than protocol 2.A
stim1.20.2 client that reaches a 1.21.0 machine shows "This stim-server speaks protocol 2, with its own authentication. Update this app or Stim, then pair again." and builds locally instead. -
Scripts. Pin
@stim-cli/server@1.21.0, not an older version, in anynpxcommand that pairs or sets up a machine: an older server prints protocol 1 pairing codes and setup that a 1.21.0 client cannot use.
Removed (breaking)
- Protocol 1 commands, flags and fields are gone (see Breaking: re-pair every device).
stim-server devices grant <id>takes only--controlor--read; build and device-host access come only fromstim-server setupwith a setup ticket, and the pending access request flow and its Allow/Deny are removed.stim-server setuprefuses--client: the ticket alone names the requesting machine.stim-server devices --jsonreplacesidentitywithfrom(a label only) andkeyFingerprint.stim-server pair --jsonprints{ qr: { v: 2, name, endpoint, pairingToken, fingerprint }, expiresAt }(without--json, the bareqrobject), and the phone checks the server key's fingerprint. The tailnet health reply is only{ "version" }. Thehellorequest dropsprotocolandauthand gains an optionalaccess(buildordevice-host), its result dropsdeviceTokenandapproval, and the error codesapproval-pendingandidentity-unavailableare replaced byunknown-secret. Instim doctor --jsonremoteMachinesanddeviceHosts, the statenode-changedis nowkey-changed. (@stim-cli/server,@stim-cli/core,stim) be8cb61ae - stim-server no longer runs a login shell to capture its environment. A variable that only a shell profile sets, such as a
STIM_*variable or a private CocoaPods onPATH, reaches the server only through its own environment,--env KEY=VALUEor--path-prepend <dir>. (@stim-cli/server) edaf8b54c stim-server setupreads the setup ticket from theSTIM_SETUP_TICKETenvironment variable and refuses--ticket, because other users on the machine can read command-line arguments. Setup removes the variable from its environment before it starts children. Stim Desktop's Add Machine command sets it, inside a PowerShell script block with$env:on Windows. (@stim-cli/server) aafaa4f05- The programmatic API error code
STIM_WORKER_FAILEDand itsstim guide errorsentry are removed (see the in-process API item below). 8174f08a3 - The
easbuild cache tier runs fixedeas-clicommands (fingerprint:generate,build:download,upload) instead of loading theeas-build-cache-providerSDK into the Stim process. It needseas-build-cache-providerin the app andeas-cliinstalled in the app or onPATH; without one the tier reportsthe EAS build cache needs an installed eas-cli executable, and Stim never installs it during lookup. A Windows.cmd/.batlauncher needs a projecteas-cliso cancellation can stop Node directly. Fingerprint and transfer commands share one deadline per phase, and timeout or cancellation kills that phase's process. EAS authentication is cached per operation. 43f8f321f
New
createStim().settings()andcreateStim().stats()read settings and stats in the calling process.settings()returns thestim settings --jsonpayload: every setting with its effective value, origin and per-layer values. An invalid environment override rejects withStimErrorSTIM_BAD_ARGand its remedy.stats()returns thestim stats --jsonpayload, with the last recorded agent-device and SwiftPM cache usage instead of measuring it again. Both run in the calling process and acceptsignal; an aborted read kills its child processes and rejects withSTIM_CANCELLED.createStimgains anenvoption (defaultprocess.env) that Stim reads its settings overrides andSTIM_HOMEfrom and passes to the operations it runs; the client's home is fixed at creation. New exported typesStimReadOptions,StimSettingsandStimStats. stim-server answerssettings.getandstats.get, and reads the stats for machine details, through these calls instead of runningstim settings --jsonorstim stats --json. c00641db0, 50548d4c4createStim().stop()runs in the calling process instead of a worker. An abortedstop()finishes its teardown and then rejects withSTIM_CANCELLED. An exception thrown byonProgressstops further progress and propagates unchanged:buildandrunare cancelled, andstop()or a read finishes its work first. 6d223b9cc- stim-server serves view-shaped status subscriptions. A server that offers them lists
status-viewsin itshellofeatures.workspaces.subscribesendsworkspacesevents with aseqstarting at 0: the first carriessnapshot({ workspaces, worktrees, archived }, with each workspace trimmed to what a list needs and the 10 newest archives), and later events carry only{ upsert, remove }deltas bypathandarchivedwhen it changed. A client that misses aseqsubscribes again.workspace.subscribewith{ workspace }sends that workspace's fullenvironment, ornull.machine.subscribesendscapacity,machine,deviceLeases,simctlAvailable,maintenance,usageandownLeases.builds.history,archived.list(optionalbeforeandlimit, at most 50, returningnext),worktrees.listandmaintenance.getreturn the full data, waiting up to 30 seconds for the server's first status.@stim-cli/core/stateexports the view derivation and keyed deltas, and@stim-cli/corethe new protocol types. Stim Desktop and the phone app follow these views, and fall back tostatus.subscribeon a server withoutstatus-views. (@stim-cli/server,@stim-cli/core) 319671da7, 593a490fc, 273683d99, e5492ad03 - stim-server runs the status watch itself, in a worker thread, from the moment it is ready, so a status subscriber gets the latest payload at once. All subscribers, device recording and paired-phone notifications share it. Before, the server started a
stim status --watch --jsonchild for its first subscriber and stopped it with the last. (@stim-cli/server) 558ce0ecb - stim-server discovers the toolchain without a login shell, and
stim doctorreports it. stim-server no longer reads the login shell's environment at start. It fills gaps in the environment it was given: it appends the default Node of nvm, fnm, Volta, mise or asdf, Homebrew,~/.local/bin, pnpm's and bun's global directories and the Android SDK tools toPATH, setsANDROID_HOMEto~/Library/Android/sdkwhen that exists, and setsJAVA_HOMEto a Homebrewopenjdk(17, then 21, then the newest) or Android Studio's bundled runtime when no JDK is installed under/Library/Java/JavaVirtualMachines. It never replaces a variable it was given. On macOS,stim doctorprints a Toolchain section with wherenode,ANDROID_HOME,JAVA_HOME,stim,brewandadbcome from and how to override each, anddoctor --jsonaddstoolchain({ entries, projectNode },nulloff macOS). When the app pins Node in.nvmrc,.node-version,.tool-versionsorpackage.jsonvolta.nodeand thenodeonPATHdoes not satisfy the pin, the expo-child and command-child dev servers run on an installed Node that does, and the metro log records aproject_nodeevent. CocoaPods without an installed.ruby-versionRuby uses rvm's or rbenv's default Ruby before falling back to the login shell.@stim-cli/coreadds a./toolchainexport. (@stim-cli/server,@stim-cli/core) edaf8b54c stim status --watchreacts to git HEAD, index and ref changes in the worktrees' repositories and to the exit of a recorded supervisor, Metro, build, browser or macOS app process (checked every 2 seconds). A log append updates the log-derived facts within about a second, and device activity at most every 5 seconds; before, a log append waited at least 15 seconds after the previous refresh. The first line does not wait for git, so worktree git summaries can benullin it and the next line carries them. On macOS, simulator boots, shutdowns, creations and deletions are detected from writes to CoreSimulator's device set, with asimctlcheck every 30 seconds instead of a poll every 5 seconds. 598598e43, 94c5db1a0@stim-cli/cache-s3shares native build artifacts and Metro values through S3-compatible storage, including Cloudflare R2. New opt-in package that owns the AWS SDK and archive dependencies, so installingstimor@stim-cli/cachedoes not pull them in. Install it in the app and select it with"cache": { "provider": "@stim-cli/cache-s3", "options": { "bucket", "endpoint", "region", "prefix" } }in.stim.json. Credentials come from the AWS SDK credential chain;cache.options.profileorAWS_PROFILEselects a named profile, and the explicit option wins. Objects live under<prefix>/builds/<platform>/<key>and<prefix>/metro/<cacheName>/<hex-key>(prefix defaultstim/v1), downloads verify SHA-256 before reuse, conditional writes keep existing build entries unless overwrite is requested, and uploads are a single PUT, so entries over 5 GiB are not supported. The transport is also exported ascreateS3FileCache().@stim-cli/cacheadds the optionalfilesprovider capability (resolveandstoreof opaque complete files, with an optionalstat) andcreateFilesystemFileCache(directory); a provider that advertises nofiles,metroorbuildscapability is refused. (@stim-cli/cache-s3,@stim-cli/cache) af0c87aeb, 9cd6c689e- Native Xcode simulator builds reuse an exact completed artifact from a paired build worker. After a local and configured-provider cache miss, Stim asks enabled paired workers for an archive whose fingerprint and full cache key match (source, Xcode, SDK, architecture and target options). A hit is downloaded and verified with no source upload, no compilation and no build slot, even when the worker's slots are full or this machine is idle. A missing or corrupt archive falls back to normal placement.
remote.buildMode: off,--remote-build localand workers without thebuild-artifact-cachefeature skip the lookup. React Native, Android, SwiftPM, compiler and dependency caches are unchanged. stim-server addsartifact: { fingerprint, cacheKey }to native Xcode build offers and thebuild.cached-artifactrequest, which needs the existing build approval, streams the archive with the same frames asbuild.artifact, and only serves the requesting client's own repository entries. (@stim-cli/server) a78694933 - Workspace links are HTTPS links that open on a paired phone as well as in Stim Desktop.
worktree warm,start,ios,androidandwebprintOpen in Stim: https://stim.appandflow.com/open?v=1&machine=<home-id>&workspace=<workspace-id>(plusplatformandslotwhen set) on stderr and inlinks.desktopof--json, instead ofOpen in Stim Desktop: stim-desktop://workspace?path=.... The link carries a persistent Stim-home ID and the workspace ID, never a path, server address or token, and opening it only navigates. Olderstim-desktop://links keep working.stim status --jsonaddsworkspaceIdon each environment, the stim-serverhelloreply addsserver.id,createStimaddsidentity(), and@stim-cli/coreexports@stim-cli/core/workspace-link(sharedWorkspaceURL,parseWorkspaceLink). (@stim-cli/core,@stim-cli/server) a63428563 createStim().build()returnsfacts.links.workspace, the HTTPS workspace link for the build's workspace, also when the build machine has no Stim Desktop installed. 68724012fcreateStimaddslogs()andfollowLogs().logs({ tail, sources, minLevel, slot, since, grep, errorsOnly, signal })returns the same merged records asstim logs --json, including hosted logs, native crashes, agent actions and Expo error context;tailbounds the result andtail: 0returns none.followLogs(options, onRecord)emits the backfill, then appended records, and resolves to a stop function; aborting its signal also stops it, and cancelling before setup rejects withSTIM_CANCELLED. Both run in the importing process, anddiagnostics()now does too instead of in a separate worker. The new type isStimLogsOptions. d560b112d, d1cea73b2server.buildsPausedandserver.devicesPausedpause incoming work on a worker. New boolean machine settings, defaultfalse:stim settings set server.buildsPaused truedeclines new builds from other machines, andserver.devicesPaused truedeclines new hosted iOS, Android and macOS sessions, with a remedy naming the setting. The running server reads them without a restart, and a pause also refuses work offered before it was set. Running builds and existing sessions continue, clients can still attach, fetch completed and cached artifacts and stop sessions, approvals are kept, and local CLI builds and devices are unaffected. (@stim-cli/coresetting,@stim-cli/server) ff6475505- An x86_64 Linux machine can take offloaded Android debug builds and run stim-server as a service. The toolchain comparison no longer requires the same CPU architecture for Android, the worker gzips the artifact in Node so GNU tar works, and the Android SDK defaults to
~/Android/Sdkon Linux (also in the CLI's own SDK lookup and the GradleANDROID_HOMEremedy).stim-server service install [--serve]installs a systemd user service on Linux (~/.config/systemd/user/dev.stim.server.service, log~/.local/state/stim/dev.stim.server.log) and prints, never runs,sudo loginctl enable-linger <user>;service update,service rollbackand remote update work as on a Mac, and a failed first install stops and removes its unit.stim-server setupon Linux approves--buildonly, refuses--device-hostand skips Stim Host and the permission steps. (@stim-cli/server) 4895e715d, 478b7b5a0 stim doctorrates each remote machine per platform. stim-server reports its OS astoolchain.osin build offers; an iOS or macOS target on a machine that is not a Mac gets oneplatformreason (finding codebuild-machine-platform) instead of every missing Apple tool, and a platform the machine cannot build at all is left out while it can build another, so a Linux worker is offloadable for Android.machine.detailsrunsstim doctor --jsonfor every platform instead of--platform ios. Doctor names the platform on a CPU mismatch. (@stim-cli/server,@stim-cli/coreprotocol type) 7543bcd79- New
@stim-cli/transportand@stim-cli/transport-tailscalepackages.@stim-cli/transportis the contract andloadTransport()loader for the transports that carry clients to stim-server; the loader never throws and reports a missing package, a differentapiVersion, a failing factory or a load over 10 seconds asunavailable. The Tailscale driver moves out of@stim-cli/serverinto@stim-cli/transport-tailscale, with no behavior change.stim-server service update --from <dir>takes the two new tarballs, a--frominstall directory is also keyed by the package files so a dependency-only change installs, and a remote update upload accepts up to 16 packages instead of 8. (@stim-cli/transport,@stim-cli/transport-tailscale,@stim-cli/server) 1039c1699 - New
@stim-cli/agent-driverand@stim-cli/agent-driver-agent-devicepackages.@stim-cli/agent-driveris the contract stim-server uses to start the tool a client's coding agent drives hosted apps with (apiVersion1,id,createAgentDriver, optionalclientandactions), with loaders that never throw andrunAgentDriverContractfor driver tests. The agent-device adapter moves to@stim-cli/agent-driver-agent-devicewith no behavior change; stim-server and the CLI resolvehosting.agentDriverfrom a registry of first-party drivers they depend on, never from a project. A hosted session stops even whenhosting.agentDrivernames a driver that is not registered. A driver that fails to load gives the client a generic notice and writes the reason, which can name paths on the hosting Mac, to stim-server's stderr, and stim-server loads the drivers' action readers on the first archived log or replay read instead of at startup. (@stim-cli/agent-driver,@stim-cli/agent-driver-agent-device,@stim-cli/server) 6e99b1c1a, 650596750 - Automatic maintenance escalates through three disk pressure levels. Level 1 is free disk under
budget.minFreeDiskGb(20 GB), level 2 under the newmaintenance.urgentFreeDiskGb(default 12), and level 3 under the newmaintenance.criticalFreeDiskGb(default 6) orbudget.hardFloorDiskGb, whichever is higher. 0 turnsmaintenance.urgentFreeDiskGbormaintenance.criticalFreeDiskGboff; level 3 still applies belowbudget.hardFloorDiskGb. At levels 2 and 3 the protection window is the newmaintenance.pressureProtectMinutes(default 10) instead ofmaintenance.protectRecentHours. Level 2 clears build outputs of workspaces unused for that window, parked simulators and emulators, and the build directories of idle worktrees (ios/build,android/app/build,android/build,android/.gradle,.build). Level 3 empties the Swift compilation cache, removes finished worktrees with the short window as grace, stops the dev server of otherwise inactive workspaces and deletes their owned devices, and deletesnode_modulesandios/Podsof idle linked worktrees, never the source checkout. Each step re-measures and stops once free disk is back above its level's floor. A directory is cleared only when git tracks nothing in it and nothing under it, nor the worktree's index, HEAD or Stim state, changed within the window.stim statusshows the level,status --jsonreportspressure.diskLevel, and every command prints one stderr line while the last check, at most 10 minutes old, measured level 3. Running dev server supervisors now trigger the disk check each minute. (@stim-cli/coresettings and maintenance types) f640b423f - Owned simulators and emulators of idle and finished workspaces are deleted. Automatic maintenance deletes the owned devices of workspaces idle for
maintenance.pressureProtectMinutesat disk pressure level 2, of any workspace idle for the newmaintenance.deviceIdleDays(default 3, 0 turns it off) in the daily sweep, and of a finished worktree that is kept, such as a dirty one, once it is idle forgc.worktreeGraceMinutes.stim gc --deletedeletes the owned devices of a finished worktree it keeps (dirty, unpushed, locked) when its workspace is idle for that grace.stim worktree removedeletes owned devices instead of parking them at pressure level 2 or 3.maintenance.keepnow also protects a workspace's devices. A workspace with no recorded use ages from itsworkspace.jsonmtime.gc --jsonaddsfinishedDevices(kind,id,name,project,slot,bytes,reason) andownedDeviceDisk(kind,count,bytes,unmeasured);stim statusprints the total as "owned devices" (size categoryowned-devices). (@stim-cli/coresetting) 11b17dfb3 stim gc --cache gradle-versionslists, and with--deleteremoves, Gradle versions no registered project pins. It coverscaches/<version>andwrapper/dists/gradle-<version>-*underGRADLE_USER_HOME, with pins read from the wrapper of each project's Android Gradle root and root. Nothing is deleted while a process that may be Gradle on that home runs, whilepsfails, while a registered project is missing or its pin cannot be read, or while the layout is unsafe; with--older-than <days>a version modified within that many days is kept.modules-2,build-cache-1,jdksand other shared Gradle caches are never deleted,--cache allnever includes this scope, and withSTIM_HOMEset nothing outside it is deleted.gc --jsonaddssections.gradleVersions(home,olderThanDays,blocked,gradleRunning,gradleRunningPids,versions[]withwillDeleteandreason,leftovers). At disk pressure level 3, automatic maintenance stops the idle Gradle and Kotlin daemons that alone block a version, then deletes versions unmodified for the newmaintenance.gradleVersionIdleDays(default 7, 0 turns it off). (@stim-cli/coresetting) 9a2ef4824- Unscoped
stim gcreports other tools' machine-wide caches. It lists Gradle version caches and wrapper dists no registered project pins, the Gradlemodules-2cache, iOS simulator runtimes with their simulator count, Xcode device support entries and agent-device runner builds grouped by agent-device and Xcode version, each with its size and the owning tool's remedy. Stim never deletes them.gc --jsonaddssections.machineCaches(measuredAt,complete,notices,gradle,simRuntimes,deviceSupport,runnerBuilds), null with any--cachescope. Measurements are cached for 10 minutes in$STIM_HOME/machine-cache-usage.json. (@stim-cli/corestate reader) 675ae601e stim gcshows each worktree's in-tree build size and lists unlisted.worktreesdirectories. Each worktree line and its JSON entry carrybuildState({ bytes, dirs: [{ path, bytes }] }): the build and dependency directories maintenance clears plus a root.pnpm-store, largest first. Sizes come from the disk-usage cache while under an hour old; otherwise oneduper directory runs, four at once with a 10 second limit, and an unfinished one shows "size unknown" (bytes: null). The new report-only section "Directories under .worktrees that git does not list as worktrees", JSONunlistedWorktreeDirectories(path,repository,bytes,remedy), names each such directory without its own git with its size and anrm -rfcommand; gc never removes them.stim worktree warmno longer copies a project-local.pnpm-storeinto the new worktree. 8120c28a4stim gc --worktreesremoves idle worktrees that have no commits of their own. A clean, pushed linked worktree whose HEAD the default branch contains and whose branch has no commit made on it is removed when idle, judged by its git and Stim activity when no Stim use is recorded. Plaingcandgc --deletestill keep it (reasonnot-merged,no commits of its own beyond origin/<default>). Automatic maintenance removes it only at disk pressure level 3, once its activity is older thanmaintenance.pressureProtectMinutesand at least 5 minutes old. Removal re-checks under the removal locks that HEAD did not move and the worktree is still idle. 7cf4e9eab- Report-only
stim gcruns nogit fetchand nogh. Plaingcandgc --jsonjudge merges from the existingorigin/<default>ref and take a pull request from thestatus --watchcache when it records the worktree's current branch and HEAD; both count as fresh for twicemaintenance.worktreeCheckMinutes, at least 10 minutes. The maintenance worktree check andgc --deletenow write their pull request lookups into that cache. A kept worktree whose verdict fresher state could change says "merge state not refreshed", and its reasonnot-mergedbecomesmerge-unknown.gc --deletestill fetches and asksghbefore removing anything. The status cache entry gainscontainsHead,endedAtandpartial. (@stim-cli/corestatus types) a80d357f0 stim-server serviceandstim-server setuprun on Windows.service installregisters a per-user scheduled task\Stim\<label>withschtasks, without elevation, that starts at logon and keeps running after sign-out (S4U); a supervisor,stim-service-runner.mjs, restarts the server 30 seconds after it exits and records its state insupervisor.json. Service files, versions and logs live under%LOCALAPPDATA%\Stim\.update,rollback,statusanduninstallwork as on macOS and Linux, andinstallwarns when the firstnode.exeonPATHis not the Node running the server (--path-prependfixes it).setuptakes--buildonly on Windows. (@stim-cli/server) 6b102672d- Windows machines take offloaded Android builds. A Windows stim-server keeps each checkout and each client's Gradle home in private short directories,
<SystemDrive>\sw\<6 hex>, so the NDK's ninja stays under 260 characters, cancels a build by ending its process tree withtaskkill /T /F, and stops the processes running from a client's Gradle home before deleting it.server.workerRootaccepts a Windows absolute path.@stim-cli/transport-tailscalefindstailscale.exeonPATHor under%ProgramFiles%\Tailscale, and@stim-cli/corekeeps a WindowsPathvariable when it extends the child environment. (@stim-cli/server,@stim-cli/core,@stim-cli/transport-tailscale) 9acaf55fb @stim-cli/core/noiseexports NoiseXXandXXpsk2handshakes with Stim's transport framing. It isNoise_XX_25519_ChaChaPoly_SHA256andNoise_XXpsk2_25519_ChaChaPoly_SHA256onnode:crypto, checked against published vectors.HandshakeOptionsandAcceptOptionstake an optionaldhto supply X25519 from the caller, for runtimes such as workerd. (@stim-cli/core) ece91237e, 9d812446bcreateStimrunsbuild()andrun()in the importing process. Before, each used a bundled worker process. Every API operation now runs in process against the client's home and project and leaves the caller's cwd, environment, output, signal handlers and exit listeners as they were. Cancellation drains the operation's own child processes.STIM_WORKER_FAILEDis gone, since there is no worker. 8174f08a3stim device provision ios [udid]registers a connected iPhone and creates or refreshes its Development profile. It is a separate, confirmed step: it finds the Xcode project the wayiosdoes, lists the team, the phone and each signed bundle id of the scheme (--scheme,--configuration), and asks [y/N]; without a terminal, or with--json, it refuses withSTIM_PROVISION_UNCONFIRMEDunless--yesis passed. It then runsxcodebuild -allowProvisioningUpdates -allowProvisioningDeviceRegistrationin a throwaway DerivedData, stops it at the first build task, before compilation, and always removes the DerivedData. It authenticates with the Xcode account or an App Store Connect API key from the new machine settingsios.appStoreConnectKeyPath,ios.appStoreConnectKeyIdandios.appStoreConnectIssuerId(STIM_ASC_KEY_PATH,STIM_ASC_KEY_ID,STIM_ASC_ISSUER_ID). Builds still never pass-allowProvisioningUpdates; signing remedies,stim doctorand the guides name the new command. (@stim-cli/coresettings) 343425025- Native Android apps can cache their APK from declared inputs. The new project setting
android.artifactInputs({ complete: true, ignored, outputs, localFiles, environment }) declares every input of a repeatable native Gradle build; with it, local builds store and reuse the APK, verified by package, signing certificate and ABI, and inputs are checked again after compilation. A configured cache provider with thefilescapability, such as@stim-cli/cache-s3, shares the APK and its receipt between machines; private input files and environment values stay local. Builds on build workers remain uncached, and without the declaration native Android stays uncached. Native Android--planchecks the same key without running Gradle. (@stim-cli/coresetting,@stim-cli/cache) 5f8cf86dd, 366de14e6 createStimaddsdoctor({ platform?, record? })andgc({ cache?, olderThan?, worktrees? }). They return thestim doctor --jsonandstim gc --jsonreports in process, within a 60 second budget and withsignal; expiry rejects withSTIM_REPORT_TIMEOUTand an abort withSTIM_CANCELLED, after owned cleanup, and cleanup that cannot verify a child rejects withSTIM_CLAIM_UNAVAILABLE. Doctor never repairs, and records a check only withrecord: true; GC is always the dry run.followLogs()takes an optional third callback,onBackfill(records), for the initial records, in batches of at most 500 when notailis given. a9334b97f, 37e4e8ab5, 56322ba6e- stim-server serves Stim Desktop's reports, settings, logs and project checks on the local socket, behind the private Desktop bootstrap:
desktop.reports.get,desktop.reports.subscribeanddesktop.reports.invalidate(shared doctor and GC dry-run reports, 60 seconds per target, with an optionalplatform),desktop.settings.get,desktop.logs.subscribe(a workspace's full retained logs),desktop.doctor.run,desktop.stop.subscribeanddesktop.reload.subscribe(streamed progress, then the CLI-compatible JSON result) anddesktop.settings.write(only on a server Desktop started), each advertised by its own feature. Phone and network clients cannot call them.machine.detailsreads its doctor, GC and stats reports in process, and its doctor read no longer records a doctor run for the workspace. (@stim-cli/server,@stim-cli/coreprotocol types) 0288e04cd, c8407e9d9, b50d08943, d7f200c1b, 422403665, 677b62ddb, a57001c7f createStimaddsreload(),setSetting()andunsetSetting().reload({ platform?, signal? })requests a JavaScript reload in the live iOS or Android app or reloads the owned Chrome page and returns thestim reload --jsonfacts, within a 60 second budget; it never retries, and cancellation cannot retract a request already sent.setSetting(key, text, { scope?, signal? })andunsetSetting(key, { scope?, signal? })share the CLI's validation and return its JSON write result with sensitive values masked; cancellation prevents a pending write. 677b62ddb, 45b166fdb, a57001c7fstim gcreports and cleans up Stim's leftovers inTMPDIR. Without--cache, gc lists Stim temp directories (stim-claim-*,stim-host-*,stim-fakehome-*,stim-hosted-macos-*,stim-server-*,stim-ws-*), xctrace recordings (instruments*.ktrace) and Jest's cache under "TMPDIR leftovers" with their size and a remedy; gc skips them whenSTIM_HOMEis set, sinceTMPDIRis shared by every home.gc --deleteand maintenance at disk pressure level 2 delete a Stim temp directory or recording only when nothing in it changed for 24 hours, it holds no live claim and it is not inside or around a registered project; recordings are kept while xctrace or Instruments runs. Jest's cache is only reported. A dead project whose state and registry supervisor records disagree is pruned once both recorded processes are gone. (@stim-cli/coremaintenance types) 6dbb3ee07
Fixes
stim status --watch --jsonprints no line when only a bookkeeping timestamp changed: eachpullRequest.checkedAt,maintenance.lastChecks,maintenance.lastPass.startedAtanddurationMs, andmaintenance.running.startedAt. A line that prints carries their current values. 3e91e6e7b- A hosted session that adopts a parked device starts with a fresh native log. Before, adoption reused the parked session's home, so its earlier native log and checkpoint were served as the new session's logs. (
@stim-cli/server) 3e915d7b0 stim worktree removebounds the git reads after teardown. When the HEAD re-check times out, it refuses and keeps the directory and the ownership record; when listing worktrees before deleting the branch times out, it keeps the branch, printskept <branch> (git did not list the worktrees in time to check it)with a retry command, and exits 1. 6b5207d94stim doctor's fingerprint parity check compares an app in a repository subdirectory with the same subdirectory ofHEAD. Before, it compared it with the repository root. 951a962d2- A native Android launch without a variant labels its verify line
debuginstead ofundefined. bdc2e08ba - A hosted iOS, Android or macOS session that the host stops before it is ready fails with
stopped before it was ready ... Retry when the host can take it; it needs no stim stop.Before, it was reported as being in an unknown state with astim stopremedy. 09535c548 - Hosted waits cover the server's worst case: the client waits up to 280 seconds for a hosted stop instead of 180, which also covers parking and the stop after a failed park, and
device-host.reservegets a deadline sized for retiring up to three parked sessions before a new device. a0edec223 stim stopor Ctrl-C ends a hosted run that is waiting for the app install or for a busy host to take the app offer. Before, those waits ran to their deadline. da0bbdc5e- An Android run whose remote cache work was abandoned returns its result before the process exits, so a
createStim().run()caller receives it. Before, the run exited from inside the launch step. c3c1e22d5 stim stopcancels aniosorandroidrun through the workspace state, and sends SIGINT only when the run's holder is a CLI process. A run inside a process that embeds Stim, such as stim-server, is asked to stop without a signal; if it does not exit within 60 seconds,STIM_STOP_BLOCKEDsays it did not exit "of the stop request" and tells you to cancel it from the client that started it instead ofkill. Ctrl-C on a run that holds a physical-device lease now cancels through its cleanup like other runs; only a second Ctrl-C, or one with no build tool or SimSlim to stop, exits at once. 87ff383b0, 8721059e6- A supervisor serving hosted Metro reacts to writes to its workspace state file instead of reading it every 500 ms, with a 10-second fallback. 173ed3e70
- A relative
XCODE_XCCONFIG_FILEin the native Xcode cache key resolves from the Xcode project's directory instead of the current directory, and the native Xcode cache key,STIM_REMOTE_BUILD,STIM_REMOTE_BUILD_MODE,STIM_ACCESS_TICKETandSTIM_TMPDIRare read from eachcreateStimoperation's own environment. 87f02e373, 4f6928850, 95dd41798 - Failed requests to remote machines are written to the workspace log of the operation that made them, so concurrent
createStimoperations no longer log into each other'sstim logs. 965cc5ffb - Cancelling a build-only
createStim().build()while it copies the app or APK out waits for the copy and its cleanup, removes the export and rejects; cancellation right after the export still refuses the result. Cancelling an artifact-cache copy stops its child, keeps the previous cache entry and removes only its own staging, a cancelled backfill no longer installs the fetched hit, and a cancelled build waiting for a build slot stops waiting. 5c4c600e1, 104faeed7, 0323f5a3e stim status --watchhandles SIGINT and SIGTERM from the start, so an interrupt before the first frame exits 0 like a later one. ba0b9c724stim gcand automatic maintenance consider every linked worktree thatgit worktree listshows for a registered project's repository, whether or not a stim command ever ran in it. Thedirtyreason now names up to three of the files that keep a worktree.stim worktree removerefuses a worktree that contains another worktree ("Refusing to remove ...: it contains the worktree ..."), and gc keeps it with the new reasonnested-worktree. 810778c09- No
stim gcinvocation starts a background maintenance pass. Before, plaingc, a report, could start one when a check was due, and inonmode that pass deletes. Report-onlygcstill prints the disk pressure level 3 warning and the maintenance plan it computes. c8d7451c4 - A maintenance pass starts at zero free disk. Each pass keeps a 1 MiB
maintenance/disk-reservefile; when taking the run claim fails withENOSPCorEDQUOT, the pass deletes it, retries once, and writes it again at its end. Before, the claim write failed and the pass exited when space was most needed. ef7eb69ec - An iOS build that used the shared compilation cache and failed with the compiler's own corruption errors (
LLVM ERROR: OnDiskCAS: corrupt internal reference,error: CAS operation failed: ...,error: CAS-based dependency scan failed: ...) empties that cache and fails with the new codeSTIM_COMPILATION_CACHE_CORRUPT, asking for a rerun; the emptying, or why Stim left the cache alone, goes to the maintenance log with trigger "ios build". Every iOS build that compiles with the cache holds a shared claim,<cache>.claimsbeside the cache directory, while xcodebuild runs, and the build,gc --delete --cache "compilation cache"and maintenance empty it only under the exclusive claim. A build worker empties its own corrupt cache; with--remote-build <machine>the build fails with this code and the worker's message, otherwise it falls back to a local build. An Android build lock no longer keeps maintenance from emptying the cache, and at level 3 a kept cache appears in the pass's blocked reasons with its size and the build. 89cb97e11, cf5775ed1, dc2d601bd stim gcmeasures each shared cache for at most 60 seconds. A cache it could not measure shows "Size unavailable: measurement failed or exceeded 60 seconds." and the totals read "at least". Before, a measurement had no time limit and an unknown size counted as 0. cee79397b- A Linux or Windows machine in
remote.machinesis used for builds only. It is never a device-pool member or hosting candidate whentailscale statusreports an OS other than macOS (an unknown OS still counts as able to host), Stim Desktop's build suggestions offer it, andstim doctor --fixnever asks or contacts it for device-host access; doctor reports it asnot-asked, even with a saved approval. A stim-server off macOS refuses a device-host connection at hello withforbidden("Hosting simulators needs a Mac. This machine takes build requests only."). (@stim-cli/core,@stim-cli/server) 2d96252ca, ce0fdd4ff, ca919c50d stim worktree removerefuses, withSTIM_WORKTREE_SERVICE, a worktree that a Linux stim-server service runs from: a systemd user unit in~/.config/systemd/user/<label>.servicewith an[X-Stim] Managed=truesection, as it already did for a macOS LaunchAgent. (@stim-cli/coreunit parsing) 46e05cb32- On Windows, the
STIM_PATH_TOO_LONGpreflight accounts for react-native-gesture-handler 3, whose shared shadow nodes have the longest known object path: such a project fits a root of at most 24 characters for an x86_64 Debug build and 21 for arm64-v8a, other projects 51 and 48. The CMake profile directory is shortened to 4 hex characters. The remedy is now to move or clone the project to a short real path such asC:\src\<name>; thesubstdrive remedy is dropped because Node and Expo autolinking resolve it back to the long path. 42b086f50 - A
createStimoperation no longer adds a processexitlistener for remote build connections; only CLI operations close those connections on exit. d716dda80 stim doctorreports a remote machine whose stim-server still speaks protocol 1 asoutdated(Remote machine <machine> runs an older stim-server) instead of unreachable, with the remedy to runstim-server service update --release <version>there and then the setup command, orstim doctor --fix; a hosted session to such a machine gets the same remedy. The offload refusal,stim guideand the docs name the setup commandstim doctor --fixprints instead ofstim-server devices grant --build. 38a9c2bf1stim doctor's check for other stim installations resolves paths and runsstim --versionfrom the calling operation's directory and environment, including itsPATH(WindowsPath), instead of the process's. fca5e6746- When
stim-server setuprefuses a server too old for setup, the remedy says to update Stim Desktop with Check for Updates when Desktop runs that server, and otherwise to runnpm install --global @stim-cli/server@<version>and restart the server. (@stim-cli/server) 9a890b33f - stim-server's
stopaction runs the in-process stop API on the exact registered workspace, including a partial one without an app manifest, instead of starting the CLI. A stop waiting for native work ends its wait when the 120 second action limit passes, while teardown already underway finishes, and the workspace stays busy until that work drains. (@stim-cli/server) e3a31955c - Following logs picks up local appends from filesystem events, with a ten-second repair scan, and hosted targets join and leave the follow stream from local state notifications, reconciled about every 10 seconds. A removed hosted slot's log follower stops. 94c665dac, 5ca538572, aad95307e
local.propertiesstays a local input when the Android project path is not canonical, such as a symlinked root or a Windows short temp path; before, it counted as a transferred source. d9243502f- The Stim Host Dev build no longer leaves clang output directories in
TMPDIR. (@stim-cli/server) 6b6cb388f - A phone event that fails validation is described with the schema of its own event name, instead of the first event type tried. (
@stim-cli/core) 0ca651da6
Docs
- CLI output,
stim guidetext, setting descriptions and the docs say "remote machine" and "this machine" instead of "Mac" where a Linux worker or a Linux or Windows client can be meant. "Mac" stays for iOS and macOS builds,stim macos, device hosting,stim-server setupandservice, permissions and Stim Desktop's own labels. JSON reason codes are unchanged. aed8cd0f6 - Guides, errors and doctor remedies name Stim Desktop's Remote Machines tab, and say Mac only where only a Mac applies and machine elsewhere, including the build pause settings and the
@stim-cli/transport-tailscaleREADME. 2d96252ca, 7dd48ca38
Migration notes
- Re-pair every phone and approve every remote machine again, after updating each machine's stim-server to 1.21.0: see Breaking: re-pair every device. Update the machines first; a phone app or
stim1.21.0 cannot pair with stim-server 1.20.2. - Do not downgrade a machine to 1.20.2 after 1.21.0 has written its pairings: the protocol 1 records are gone, and it would need pairing again too.
- A
STIM_*variable or tool path that only your shell profile sets no longer reaches stim-server. Pass it with--env KEY=VALUEor--path-prepend <dir>, or set it in the service's environment.
QA
The maintainer chose this minor version and approved the release without a QA gate on 2026-10-14. Pre-tag QA is omitted, as with 1.18.0 to 1.20.2. This deviates from RELEASE.md section 3, which has no waiver for a stable version, and from docs/release-recovery.md, which asks for the full lane on the first release that contains a new package (@stim-cli/cache-s3, @stim-cli/agent-driver, @stim-cli/agent-driver-agent-device, @stim-cli/transport and @stim-cli/transport-tailscale).
Omitted before the tag, all eight rows of the matrix (node scripts/release-qa-matrix.mjs v1.20.2; 1133 changed paths, 438 mapped to a row, 0 unclassified): loop, logs, caches, real-repository, remote-provider, release-build, android-flavor and launch-evidence, plus the native suites run-native-e2e.mjs and run-cache-e2e.mjs. The protocol 2 re-pairing steps above were not exercised by hand on this candidate.
Run before the version commit: format:check, lint, build, typecheck, knip, release:prep --check, test:runtime, test:e2e (115 of 115), pnpm test (9323 tests: 9296 passed, 25 skipped, 2 failed locally in files this release does not change: packages/stim-cli/src/__tests__/warm-claim.test.ts "the installer process group keeps the claim after the refresh that spawned it is gone" failed in the full run and passes alone, and scripts/agent-benchmark/macos-processes.test.mjs fails at its sandbox-exec call on this Mac), CLI version/help/guide, licenses:check and format:check in apps/mobile, the QA matrix's own tests, and pnpm tarball inspection of the twelve packages. Not run: test:compat, which needs a connected iPhone.
A first v1.21.0 tag failed the Release workflow's full Windows gate and published nothing; it was deleted and the tag moved to a later commit on main. #3868 and #3871 fix or quarantine the Windows failures; the tests quarantined on Windows CI are listed under #3878, the parent issue for the 1.21.1 Windows patch. For this release the Release workflow runs the full Windows suite without gating publication on it (#3906, the maintainer's decision on 2026-10-15). The random Vitest worker crashes in the Windows suite (#3877) remain.
Run after push: exact-commit CI, with the tests quarantined on Windows CI skipped, and the Release workflow's own gates, with its Windows suite reporting but not gating.
1.20.2
Stim Desktop and the Stim phone app ship through their own workflows; this release covers the seven npm packages (@stim-cli/core, @stim-cli/cache, @stim-cli/metro, @stim-cli/expo-build-cache, stim, @stim-cli/ci and @stim-cli/server). Commits that only changed those apps, tests or internal structure are not listed.
New
workspace.hiddenhides a workspace in Stim Desktop and the phone app. New boolean setting, workspace scope only, defaultfalse:stim settings set workspace.hidden truefrom the workspace.stim status --jsonthen reportshidden: trueon that environment. The nextstart,ios,android,web,macos,reloadorworktree warmin the workspace clears it, andworktree removedrops it with the workspace. (@stim-cli/coresetting and status type) 1186ed44d
Fixes
- Security: on a checkout without its own dependencies, such as a fresh worktree or a hoisted monorepo app,
stim doctorskips the fingerprint parity check and says why when Expo packages resolve from outside the app, or when the check would neednpx react-native config(Android, or a project without aniosdirectory). Before, that check could run project code that doctor does not install or own. The linked-library check can still run installed Expo packages on the main checkout, as a build does. 26a22d93c - Security: a cache registration whose directory is a filesystem root, or is or contains the home directory, the system temporary directory or the Stim config directory, is refused: the CLI's registration throws
cannot register this cache: ...,@stim-cli/coreregisterCacheignores it, andstim gcnever prunes or empties such a directory, reporting it as not a directory the cache owns. (@stim-cli/core) a6c9ba3db - Security: a remote
server.update.startfrom a build or device-host client refuses a release older than the running stim-server withbad-request, so a client with an older stim can no longer move a remote Mac back to it.stim-server service updaterun on the Mac itself is unchanged. The README andstim guide settingsnow say that a build grant is full access to the account stim-server runs as, since project code runs there unsandboxed. (@stim-cli/server) fa167612a stim gclists stale entries of the machine EAS session ledger (~/.stim/machine/eas/sessions.json) under "Stale EAS ledger entries": an entry whose id is not a UUID and whose absolute workspace root is missing on a mounted volume, such as ids written by a test fixture. A valid id, an existing root, or a root on an unmounted volume keeps the entry.stim gc --deletere-checks each entry and removes it under the ledger lock without contacting EAS.gc --jsonaddssections.staleEasLedgerEntrieswithidandworkspaceRoot. 2b0e36d5b- Every child process Stim starts has a time budget. Git calls default to 10 seconds, read-only ones run with
--no-optional-locks, and asynchronous reads run at most six at a time. The git reads that gatestim worktree removeget 60 seconds; when one times out, removal refuses before any teardown witha git safety check timed outinstead of treating a registered worktree as a plain folder.stim gcreports a worktree whose unpushed commits or initialized submodules could not be checked as skipped with that reason. 5f56cc4dc, bbb96cb96 stim status --jsonreports a workspace whose macOS app is running or orphaned asstage: running, from the app's build end when no Metro supervisor runs. Stim Desktop and the phone app now show the stage, app presence and git chip that stim reports instead of computing their own. (@stim-cli/core) 14c46bf48- stim-server build uploads:
build.syncfails withbuild-busywhile the worker root's volume has less than 10 GiB free and withlimit-exceededonce one manifest's sizes add up to more than 20 GiB, and a blob that would leave the volume under 10 GiB free closes the connection with 4400. When a client's build ends and no other build of that client runs, the server deletes the client's blobs that no checkout and no open manifest uses; blob pruning stops without deleting anything when a mirror record cannot be read. A revoked build client's directory under the worker root is deleted once its builds end, unless the client has not connected since the server started. (@stim-cli/server) fdae5685c, f87b6115b stim macoswaits up to 20 seconds for macOS LaunchServices to list the launched app before it returns, soagent-device openattaches to it instead of starting a second copy without Stim's launch arguments. If the app is still unlisted, it warns and returns; a--remotelaunch skips the wait.stim guide macosand the website describe the wait instead of the manuallsappinfoloop. 0d5bbf727, a3a4c9fd6- Relaunching an Android app whose process state cannot be read launches with
am start -S, which force-stops the app as part of the start. Before, Stim launched without stopping it, so a running app could keep its old process and bundle. An app without a launcher activity gets a best-effortam force-stopbeforemonkey, and a known running pid still gets a separate force-stop that refuses the launch when it fails. 313f2c1a0 - A hosted install waits 355 seconds instead of 300, covering the host's install deadline plus its settle bound; the client's hosted waits now derive from the server's limits in
@stim-cli/core. b5c6b9d86 - The Release workflow refuses to publish a tag whose commit is not on
main. This changes the release process, not a package. 2bbf06e69
Docs
stim guide tutorial runhas the agent check the change by picking a size (size-s,size-morsize-l) instead of typing a name. e50e7b25b
QA
The maintainer approved this release on 2026-10-10. Pre-tag QA is omitted, as with 1.18.0 to 1.20.1. This deviates from RELEASE.md section 3, which has no waiver for a stable version.
Omitted before the tag, all eight rows of the matrix (node scripts/release-qa-matrix.mjs v1.20.1; 250 changed paths, 87 mapped to a row, 0 unclassified): loop, logs, caches, real-repository, remote-provider, release-build, android-flavor and launch-evidence, plus the native suites run-native-e2e.mjs and run-cache-e2e.mjs.
Run before the version commit: format:check, lint, build, typecheck, knip, release:prep --check, CLI version/help/guide, licenses:check in apps/mobile, and pnpm tarball inspection of the seven packages. Not run locally, left to CI: pnpm test, test:e2e, test:runtime and test:compat. Run after push: exact-commit CI, including the full Windows job, and the Release workflow's own gates, including its new tag-on-main check.
1.20.1
Stim Desktop and the Stim phone app ship through their own workflows; this release covers the seven npm packages (@stim-cli/core, @stim-cli/cache, @stim-cli/metro, @stim-cli/expo-build-cache, stim, @stim-cli/ci and @stim-cli/server). Commits that only changed those apps, tests or internal structure are not listed.
Fixes
- npm install and npx no longer print the uuid@7 deprecation warning; Xcode project files are read by an in-tree parser.
stimdrops thexcoderuntime dependency that 1.20.0 added and keeps@xmldom/xmldom. e8f149943
Docs
stim guide tutorial runhas the agent check each change on the simulator with agent-device after the after screenshot (tap the button, toggle the switch, type a name, take a screenshot), so Stim Desktop records the actions for replay; when agent-device is not installed it checks the build result andstim logs --errorsinstead and does not install it unasked. The "Change It Again in Parallel" step has a copyable prompt that asks for a new worktree. Thestim guide tutorial manualsection is removed and now answers "Unknown section"; the website's Desktop and getting-started pages no longer link it. 52eba84c7- The
ios.schemedescription instim settingsand the shipped JSON Schema says the--schemeflag overrides it, and the website's settings tables are generated from the settings registry. (@stim-cli/core) b52e08b6a
QA
The maintainer approved this release on 2026-10-10. Pre-tag QA is omitted, as with 1.18.0, 1.19.0 and 1.20.0. This deviates from RELEASE.md section 3, which has no waiver for a stable version.
Omitted before the tag, all eight rows of the matrix (node scripts/release-qa-matrix.mjs v1.20.0; 76 changed paths, 7 mapped to a row, 0 unclassified): loop, logs, caches, real-repository, remote-provider, release-build, android-flavor and launch-evidence, plus the native suites run-native-e2e.mjs and run-cache-e2e.mjs.
Run before the version commit: the native Xcode acceptance job (Native E2E, native_ios_only, fixture project) on main at eafc8cc9f, which includes the new parser: run 38083829755, passed. Also format:check, lint, build, typecheck, knip, release:prep --check, CLI version/help/guide, licenses:check in apps/mobile, and pnpm tarball inspection of the seven packages. Not run locally, left to CI: pnpm test, test:e2e, test:runtime and test:compat. Run after push: exact-commit CI, including the full Windows job, and the Release workflow's own gates.
1.20.0
Stim Desktop and the Stim phone app ship through their own workflows; this release covers the seven npm packages (@stim-cli/core, @stim-cli/cache, @stim-cli/metro, @stim-cli/expo-build-cache, stim, @stim-cli/ci and @stim-cli/server). Commits that only changed those apps, tests or internal structure are not listed.
New
- Native Xcode and Gradle apps build through project recipes. The integration registry gains a native Xcode provider and a native Gradle provider, each with its own planning and doctor checks. Xcode selection reads projects, workspaces, schemes and configuration files; Gradle reports application variants through a checked-in model shim. Both use native process readiness, so a live native Debug or custom-configuration app reports
launched: truewithout Metro. Only proven Xcode input closures qualify for whole-artifact reuse; native Gradle is not artifact-cache eligible.stim guide agentroutes native apps to the newstim guide lifecycle native-iosandnative-androidsections.stimgains two runtime dependencies for reading Xcode projects,@xmldom/xmldomandxcode. 82e649b4c - Native iOS apps run on hosted simulators. A hosted iOS session negotiates the host capability
hosted-ios-processand waits for live process readiness instead of a Metro bundle. A host without it is refused before reservation or upload, with an update-stim-server remedy, and--remote autoskips it. React Native modes keep bundle readiness. (@stim-cli/coreprotocol,@stim-cli/server) db536eb72 - Native Android apps run on hosted emulators. The host capability is
hosted-android-process. A native Gradle app uses an approved hosted Android emulator without starting Metro, and a host without the capability refuses before admission or APK upload. The delivery reuses verified APK transfer and package, SDK and ABI checks. React Native development still requires Metro. (@stim-cli/coreprotocol,@stim-cli/server) 8f1bd25d1 - Native Xcode builds can run on an approved build worker. The client and worker negotiate the capability
native-xcode-build. Only the contained, Git-visible inputs of the provider snapshot are transferred, and the worker mirrors them exactly. A required ignored or external input, or an unproven cache identity, refuses and the build falls back to local. The client sends its identity parameters withbuild.start, and the worker refuses early withidentity-mismatchnaming the differing parameter (for exampleDEVELOPER_DIR).LANGandLC_ALLare no longer part of the artifact key. (@stim-cli/server) 85ae2d460 - Native Gradle builds can run on an approved build worker. The capability is
native-gradle-build, and the project declares what to transfer with the new project settingandroid.offloadInputs:{"complete": true, "ignored": [...], "outputs": [...]}. The worker receives Git-visible source plus the exactignoredfiles, and every AGP-reported build directory the build creates must be listed inoutputs. External inputs, directory links, submodules, a rootbuildSrcand custom machine properties refuse. An undeclared output directory fails the remote build by name and falls back to a local build. The APK has no Stim artifact cache identity; Gradle's own incremental and configuration caches are kept. Seestim guide lifecycle native-android. (@stim-cli/server) d96f6531b ios.schemenames a project's Xcode scheme once. New project setting (workspace and committed layers).--schemestill wins when passed; otherwiseios.schemeis used bystim ios,--plan, the native Xcode doctor and thestim buildAPI, so the plan and the run agree on the cache key. An--eas-profilebuild reads only the flag. 3d93ba64dios.projectPathlocates a bare React Native app's Xcode project outsideios/. New project setting (workspace and committed layers; defaultios), a directory relative to the app.stim ios,ios --planandstim buildrefuse withSTIM_BAD_ARGa path that escapes the app, is absolute, or holds no.xcworkspaceor.xcodeproj, and any value on an Expo app. The path is added to the fingerprint, travels to build workers in the build job, andworktree removerestoresPodfile.lockand the app's own<name>.xcodeproj/project.pbxprojchurn there. 97ca1663eandroid.gradleRootandandroid.modulebuild a bare React Native app from a Gradle root outsideandroid/. New project settings (workspace and committed layers).android.gradleRoot(defaultandroid) is a directory relative to the app that holdsgradlewandsettings.gradle; it may be outside the app but not the repository.android.module(default:app) is the app's Gradle project path. With either set, Gradle runs from the root with the qualified task (for example:packages:rn-tester:android:app:assembleDebug). Both join the cache key as a hashedgradleProjectcomponent (@stim-cli/corebuildCacheKey) and travel in the remote build job.stim android,--planandstim buildrefuse withSTIM_BAD_ARGa root outside the repository or withoutsettings.gradle(.kts), a module directory without a build script, a malformed module path, and either value on an Expo app. (@stim-cli/serveraccepts the two fields inbuild.start.) 7480ff5d2metro.commandstarts a project's dev server with its own command. New project setting (workspace and committed layers): an argv array, run without a shell from the app directory, where{port}is replaced with the workspace's reserved port. The supervisor uses a third server mode,command-child, ahead of ecosystem detection, runs the command in its own process group, and signals the group on every stop path. Output is logged ascommand_stdoutandcommand_stderrevents and counts as dev-server activity formetro.idleStopMinutes.stim startrefuses withSTIM_BAD_ARGan empty argv, a blank program, a missing{port},--reset-cache, and Windows. Stim adds no bundle-response reporter to this server, sostim iosandstim androidreport the launch as unverified; readstim logs --source metro.startand the iOS and Android launch facts now report the supervisor's recorded mode. 7fcfebd09stim.build()in the public API builds an app without launching it. It builds Android APKs, iOS simulator apps and macOS apps with the same provider recipes, cache validation and build ownership asrun(). The result is an owned workspace copy kept untilstim worktree remove. A build-only result is recorded only in build history and in its ownbuild-artifact-<platform>.ndjson, so a running app keeps its last-build record, device record and build log, and cache maintenance protects the newest succeeded build key per platform so a laterruncan reuse it. Seestim guide api. 7d92c3395stim-ci buildandbuildCIproduce an app artifact without starting a runtime. (@stim-cli/ci)stim-ci build --platform ios|android|macoscalls the public build API and exports an Android APK (app.apk) or an Apple app archive (app.tar.gz, with executable modes and symlinks preserved) into--artifacts, withbuild.json,result.jsonanddiagnostics.json. A laterrunin the same job can reuse the build from the same Stim cache and reportscacheKeyandcacheHit. Cancellation waits for the owned build to finish cleanup and never calls workspacestop(). New error codes:STIM_CI_BUILD_FAILEDandSTIM_CI_ARTIFACT_FAILED. Importing a downloaded artifact across jobs is not supported. 90d4d10d1- Memory pressure names the process that holds the memory. When macOS reports warning or critical pressure, Stim reads
top -l 1 -o mem -n 20and flags a process at or above the larger of 8 GiB and 25% of RAM, or 2 GiB forfseventsdand Watchman. The flagged process is named in the iOS boot warning and failure advice, the Android memory phase line and boot-timeout remedy,stim doctor(findingmemory-culprit, skipped whenSTIM_HOMEis set), and the maintenance pressure check, which stores it inmaintenance.pressure.memory.culpritsinstim status --json(fieldspid,name,bytes,command,note) and shows it on the plainstatusmaintenance line. A command is attached only for processes known to be safe to restart:sudo killall fseventsdforfseventsd,stim gc --delete --cache watchmanfor Watchman, andstim gc --delete --cache gradle-daemonsfor Gradle and Kotlin daemons. Stim never runs these commands itself. 8549b4ff1 stim statusshows what the device and launch phases wait on.stim status --jsonbuildgains an optionalactivity: { name, startedAt, percent? }, wherenameisbooting(the simulator or emulator boot this run started),bundling(from the app's bundle request on this workspace's Metro until Metro delivers it;percentis Metro's own progress for that request) orwaiting-ready(the app reports its readiness pending). Plainstim statusprints, for example,build: ios launch (bundling JS 45%, 12s), 58s elapsed.launchedvalues are unchanged. A boot on an EAS-hosted simulator is not reported asbooting. Stim Desktop and the phone app show the same label. ca5b5017bstim status --jsonreportsdoctorRunson an environment:{ ios?: { at }, android?: { at } }, the timestim doctorlast ran for each platform in that workspace; the field is absent until doctor has run. 465eb1c5bstim guide tutorial: the "Build on Another Mac" step is removed, the begin prompt is shorter, and a new optional last step deletes the test app. The newstim guide tutorial deletesection checks thestimTutorialmarker in{base}/app.json, removes each linked worktree and then{base}withstim worktree remove(never--force), and only then runsrm -rf "{base}". The tutorial version is unchanged. b92e8bc1c
Migration notes
No command, flag or setting is removed. These behaviour changes need attention.
@stim-cli/serverrefuses WebSocket upgrades from web pages. An upgrade that carriesSec-Fetch-Site, or anOriginother than the requested host (http://<Host>with a loopback host name on loopback,https://<Host>throughtailscale serve), now gets HTTP 403 before the auth-failure limiter, without spending the failure budget. Upgrades withoutOrigin, such as Desktop and the CLI, pass. Known gap listed in the PR: an iOS simulator app paired tows://[::1]:...sendsOrigin: http://::1:...and is refused;127.0.0.1andlocalhostwork. A pending approvalHelloResultno longer carriesserver.home, which is now optional in the coreHelloResulttype and the JSON schema. Paired phone names are cleaned (control and format characters removed except U+200D, whitespace collapsed, cut to 64 UTF-16 units, empty becomesPhone). Previously a web page could fill the shared'local'limiter bucket and lock out loopback clients for 60 s, and a page on any tailnet node could requestbuildaccess. 6b69ad01aandroid.keystorePasswordno longer reaches apksigner's argv, and Stim state is owner-only. A literal password is passed as--ks-pass env:STIM_KEYSTORE_PASSWORDwith the value only in the child's environment;env:,file:andstdinreferences pass through. Stim replaces those values with***in a thrown error, so a failed signing step cannot write the password to the workspace build log, which paired devices withreadcan fetch.$STIM_HOME, workspace state directories and each ndjson log directory are created 0700 (an existing wider directory is narrowed when the filesystem allows),config.jsonandcreated-devices.jsonare written 0600 through a temp file, and new ndjson logs open 0600.@stim-cli/coreexportsensurePrivateDir. Existing log files keep their mode. 8eeac50b8stim doctorno longer executesfingerprint.config.jsor the project'seas-cli. Whenfingerprint.config.jsor.cjsexists, doctor skips both fingerprint checks and reportsSkipped the fingerprint checks: fingerprint.config.js is code. The project eas-cli version is read fromnode_modules/eas-cli/package.json, andeas whoamiruns only with aneasonPATHthat is outside the git root by path and realpath, not in anode_modules/.binabove the project, and with a known repository root. The parity check's temporary worktree runs withcore.hooksPathpointed at an empty path.findBinUpwardstops at the git repository root, which builds also see: a hoisted monorepo that is not a git repository no longer resolves a parent.binshim forexpoBinPathandresolveEasCliBin. Doctor still can run installed packages through@expo/fingerprintandnpx react-native configin the parity check; the PR lists this as open. e18fbec7c- Reclaim no longer stops processes on a workspace's named ports.
stim gc --delete,stim worktree removeand background maintenance release a reserved named port (8900-8999) without looking up or signalling its listener.stim ports stopis unchanged. A server a worktree started withstim ports getkeeps running after removal; stop it first withstim ports stop. Before this change, any process that later bound the port, for example Solr on 8983, was killed. 8c6b3816c - A package that lists
react-nativeonly underpeerDependenciesnow counts as an app. In a monorepo, a shared package such aspackages/uiwith such a peer dependency is its own workspace, sologs,stop,reload,settingsandworktree warmrun from it act on that package; run them from the app directory. Detection reads onlypackage.json, sometro.commandset at either the committed or workspace scope works in such a package. 680afc64c - One-shot
stim statusreads git only for live environments. Idle environments and unprovisioned worktrees reportworktree.git: nulland nogitChip;stim status --watchstill reads every worktree. 08857a71d stim status --watch --jsonprints fewer lines. A usage figure keeps its previously printed value until it moves by a step: an owner'scpuPercentby 5 points, an owner's or environment'smemoryMbandcapacity.committedMbby 16 MB. A line is still a complete payload. In a watch linecapacity.committedMbis held separately from the environments' sum. 88ff46f06stim-server service install --envvalues no longer appear in the server's process arguments. (@stim-cli/server) Each--env KEY=VALUEis written to the LaunchAgent plist'sEnvironmentVariables, andProgramArgumentscarries only--env KEY, so a value such asEXPO_TOKENis no longer readable withps. The values stay in plain text in the plist, and launchd now also passes them to the server's own Node process, so a variable such asNODE_OPTIONSgiven with--envaffects the server too.service installandsetuprefuse--envforSHELLas well asSTIM_HOME. A plist written by an earlier release keeps its values in the arguments until the nextservice install;service updateandrollbackonly replace the script path. 9e9f6dc4a- A committed
android.keystoremust resolve inside the repository. A value in the committed.stim.jsonthat is an absolute path outside the repository, or a relative path or symlink that escapes it, is no longer used: the release APK swap is skipped with aswapnote and the run builds fresh. Set the path at workspace or repo scope to keep a key outside the repository. The Android package id read from the Expo config orbuild.gradlemust now look likecom.example.app; one that does not, such asapplicationId "${appId}", counts as not detected, andstim androidrefuses before anyadbcall when neither the APK nor the project names a valid package. A__proto__key in a committed.stim.jsonor inconfig.jsonproject entries is ignored. a6546a431 - On Windows, Stim refuses to run a
.bator.cmdtool whose arguments contain a double quote, carriage return or line feed, so an argument cannot break out of the batch file's argument re-expansion.android.deviceProfilemust now be an avdmanager id matching^[A-Za-z0-9 ._()-]*$(an empty value still counts as unset; real ids such aspixel_6,Nexus 5Xor10.1in WXGA (Tablet)pass);stim settings setand settings validation refuse anything else. The remainingdf, simctl, avdmanager, adb, emulator and lsof calls pass their arguments as argv instead of a shell string. 8d2ab2f91
Fixes
stim macosrefuses amacos.infoPlistthat is a symbolic link or is not a regular file inside the directory it is relative to, and an offloaded build refuses an artifact that the worker's archive turns into a symlink (or a symlinkedContents) or places outside the staging directory; the error names the worker. 99e421b75- CocoaPods runs with the login shell's Ruby environment when no project
.ruby-versionRuby is installed. Stim takesGEM_HOME,GEM_PATHand the Ruby-relatedPATHentries from$SHELL -lic(10 s timeout, cached per shell), so thepod --versionprobe,pod installand the Bundler environment agree, and remote build placement no longer compares a caller's bundled CocoaPods 1.16.2 against a remote Mac's 1.17.0 and falls back to a local build. An installed.ruby-versionRuby, an existingGEM_HOME, and Bundler-pinned projects are unchanged. 55f102ca9 - Relaunching an iOS app on a simulator whose process state cannot be read (the
launchctl listprobe times out) launches withsimctl launch --terminate-running-processinstead of a separatesimctl terminate, so a just-booted simulator no longer fails withSTIM_LAUNCH_FAILEDwhen terminate times out. A dev client in that state launches through--initialUrlrather thansimctl openurl. A known pid still gets a separate terminate. Emulators still launch without stopping anything in that state. 93fbdbf37, 076d8b2dc - Owned-AVD teardown bounds its
emulator -list-avds,adb devicesandemu avd nameinventory queries and kills a timed-out query child. The budget is 15 s per resolver; a timeout fails the teardown and keeps the device record. d7a4f3355, 63170af22 - Temporary Android build artifacts (the APK copy) are released on every exit, including cancellation and device boot or install failure, in the run, build-only and hosted paths. Stable exports and cached APKs are kept. 723801fcb
- Hosted preparation waits 355 seconds before it is refused, where it gave up after 180 seconds although the host allows its preparation worker 300. A resumed session that starts stopping keeps its separate 180-second stop budget. c194a510f
- Hosted Android boot failures include the session device's boot-property and package-manager observations in the preparation and adoption failure notices, without host-wide device inventory. 1a058eb73
- Native Xcode worker offers collect only the Xcode, simulator SDK, CPU and Stim matching data through the negotiated capability
native-xcode-toolchain, instead of CocoaPods, Bundler and other platforms' SDKs. Older native-build-capable hosts still return the complete report. (@stim-cli/server) 48f161a6a - Hosted Debug runs that start Metro no longer refuse with
metro.ngrokUrl requires metro.tunnel to be "ngrok"(STIM_BAD_ARG) when no layer setsmetro.ngrokUrl; anundefinedngrokUrlleft by the hosted overlay counts as unset. 227bddc30 - Structured Expo bundle-response records (those with a string
requestId) skip the one-second log dedup, so a droppedbundle_response_finishedno longer leaves a request open forever and blocksmetro.idleStopMinutesfrom stopping that dev server. b111e54d7 stim statusruns at most six git reads at once (the 3 s timeout starts when a read starts), andtailscale status --jsonfor a hosted session is read asynchronously instead of withspawnSyncon the main thread. Before, a one-shotstatus --jsonwith 147 worktrees and environments spawned 147 child processes at once. 08857a71dstim doctorstarts its Watchman, device-host, watched-checkout and build-machine probes together, and the remote Mac toolchain comparison (Xcode, simulator SDK, login-shell Ruby,pod --version, simulator runtimes) no longer blocks the event loop. Findings, JSON fields and their order are unchanged. 69044fb11@stim-cli/serverusage history keeps the 15-second slots filled between status lines, so the phone's 10-minute usage chart has no gaps whilestatus --watchprints nothing for a steady workspace. 88ff46f06- Two kill sites now check the recorded process identity before signalling:
stim start's Windows supervisor handoff signals the recorded supervisor only while it is still the same process, and otherwise reports the remedy withstim stopand thetaskkillorkillcommand to run; Chrome cleanup forstim webtreats a leftover process group as lingering only when a member still runs the workspace profile, so an unrelated process that reused the group id is not signalled. 09bc84d51, 4d285909e - Windows: the "ccache off" note now prints the module build script path with consistent separators. c2e077139
server.update.status(@stim-cli/server) reports how an update this server started ended, including one the update lock refused, with the last line of its log, when that is newer than the recorded last update. fcaddb553
Docs
- The website,
stim guideand the package READMEs coverios.scheme,ios.projectPath,android.gradleRoot,android.module,android.offloadInputs,metro.command, native Xcode and Gradle apps (stim guide lifecycle native-iosandnative-android), hosted native apps and native build offload,stim.build(),stim-ci build, memory-culprit reporting, thestatusgit and watch behaviour, named ports on reclaim, whatstim doctordoes not run, and the CocoaPods environment used for placement. 82e649b4c, 90d4d10d1, e18fbec7c, 8c6b3816c, 55f102ca9 stim guide tutorial runhas the agent take before and after screenshots of each change into.expo/screenshots/, andstim guide tutorial sharebuilds the pull request body from the tutorial repository's template with those screenshots attached. 14427a8a4stim guide tutorial runreuses an existing tutorial folder when it is a clean clone of the tutorial repository at the current tutorial version, instead of always asking for a new folder; any other existing folder still stops and asks. 0c2d42489
QA
The maintainer approved this release on 2026-10-10. Pre-tag QA is omitted, as with 1.18.0 and 1.19.0. This deviates from RELEASE.md section 3, which has no waiver for a stable version.
Omitted before the tag, all eight rows of the matrix (node scripts/release-qa-matrix.mjs v1.19.0; 366 changed paths, 162 mapped to a row, 0 unclassified after this release adds a rule for vitest.reap-children.ts): loop, logs, caches, real-repository, remote-provider, release-build, android-flavor and launch-evidence, plus the native suites run-native-e2e.mjs and run-cache-e2e.mjs.
Run before the version commit: format:check, lint, build, typecheck, knip, release:prep --check, CLI version/help/guide, licenses:check in apps/mobile, and pnpm tarball inspection of the seven packages. Not run locally, left to CI: pnpm test, test:e2e, test:runtime and test:compat. Run after push: exact-commit CI, including the full Windows job, and the Release workflow's own gates.
1.19.0
Stim Desktop and the Stim phone app ship through their own workflows; this release covers the seven npm packages, including the new @stim-cli/ci. Commits that only changed those apps are not listed.
New
- Auto placement can fall back to an EAS Simulator. New setting
remote.easFallback(defaultfalse, machine or project scope). Whenios.remoteorandroid.remoteisauto, this Mac is at itsconcurrency.maxDevicescap and no Mac inremote.machinestakes the run, the simulator or emulator runs on a billed EAS Simulator through the existing--remote easpath instead of waiting or refusing withSTIM_AT_CAPACITY. A Mac that is only busy with a free slot never bills. A read-only check runs first (eas-cli and agent-device present, no conflicting flags or EAS session, a reachable Metro origin,eas simulator:availability); when it fails, placement behaves as before. The placement record says why EAS was or was not used, anddevicePlacement.decisioncan beeas. An EAS placement keeps billing untilstim stop.--planwith auto refuses withSTIM_BAD_ARGwhen it would use EAS. abfca8b29 localis a reserved value forios.remote,android.remoteand--remote. It runs the device on this Mac and overrides anautoset at a lower layer. A tailnet Mac namedlocalcan no longer be targeted by--remote. 10ff88a79- A rewritten
stim guide tutorial(tutorial version 2), which Stim Desktop's tutorial follows. It clonesappandflow/stim-tutorial, has the agent make a change and a parallel change in two worktrees, then covers live view, agent actions, logs, building on another Mac, an optional pull request to the tutorial repository, and removing the two worktrees. Every tutorial iOS run usesstim ios --remote local --remote-build local, so its builds and devices stay on this Mac; the optional machine step builds on the named Mac with--remote-build. 2c9b5a259, 10ff88a79 - New package
@stim-cli/ciwith astim-ciexecutable.stim-ci run --platform ios|android|macos|web -- <test command>builds and launches the app through the publicstimAPI, runs the test command, keeps its exit status, stops the workspace, and keeps structured results and diagnostics in--artifacts. A managed GitHub Action in this repository (.github/actions/stim-ci) wraps it and uploads those results as a workflow artifact. On GitHub-hosted runners with no explicit home it uses a job-local Stim home and build cache under$RUNNER_TEMP/stim-ci. New error codes:STIM_CI_SETUP_FAILED,STIM_CI_TEST_FAILED,STIM_CI_TIMEOUT,STIM_CI_CANCELLED,STIM_CI_CLEANUP_FAILED,STIM_CI_DIAGNOSTICS_FAILEDandSTIM_CI_REPORT_FAILED. See the package README and the website's CI page. 1cf1c7403, 808af7c4f, 56317a90d - Automatic pool members can be turned off. New machine settings
remote.buildPoolDisabledandremote.devicePoolDisabledlist members (localor exactremote.machinesentries) that this Mac's automatic build or device placement skips. Explicit placement and running builds or sessions are unaffected, and each pool must keep one enabled member; when every enabled member is unavailable, Stim refuses instead of using a disabled one. d6f652965 stim macos --plan --jsonchecks the declared packaging inputs without building, launching or contacting a worker, and prints one typed JSON payload, including for refusals. f6f18f958stim doctorreports worktrees nested in a checkout that Watchman would crawl (findingwatchman-nested-worktrees).stim doctor --fixadds the worktrees' parent folder, or each worktree, to the checkout's.watchmanconfigignore_dirs. Doctor never runswatchman watch-del; it prints the command when Watchman already watches the checkout. 8ebe432bdstim status --jsonreportsagentDevice.installed, whetheragent-deviceis on PATH. 2c9b5a259
Fixes
- Faster port allocation. Metro and named-port allocation read the OS TCP listener table once (
netstaton macOS and Windows,/proc/neton Linux) instead of probing each candidate. A named port is allocated in about 5 ms where 1.18.0 took 90 ms, or 800 ms with IPv6 listeners. Metro allocation now also detects IPv6-only and LAN listeners, which 1.18.0 missed and could pick an occupied port. When the listener table cannot be read, allocation fails with the new error codeSTIM_PORT_INSPECTION_FAILEDrather than guessing, and on Windowsstim ports stopkeeps reservations instead of removing them. a30008561 stim statusno longer piles upduprocesses. A checkout is measured without the worktrees nested in it, which have their own figures, soworktreeBytesfor such a checkout is smaller. Only oneduruns at a time on the machine, atnice -n 19, and a failed or timed-out walk is cached for the same period as a success. This removes the load spikes when severalstim status --watchinstances ran at once. 0e25072d2stim stopstops every running copy of astim macosapp, not only the one it recorded, including copies opened from Finder or the Dock. Relaunch and workspace removal do the same, andgc --deleterefuses while an unrecorded copy runs. A copy whose executable cannot be confirmed refuses the stop with the new error codeSTIM_MACOS_OWNER_UNVERIFIED. The stop waits up to 12 seconds for the supervisor, so a slow-exiting app is no longer left running. 79c7cc590stim worktree warmrun from a worktree root records the agent on the app workspace it prepares, so Desktop attributes the agent's actions to that workspace. b04479cfc- Hosted iOS and Android devices report the
device,installandlaunchbuild phases while they are placed on another Mac, sostim status,stim statsand Desktop show the step in progress. de7e8a659 stim stopcancels an offloaded build in progress and reportsSTIM_CANCELLEDinstead of starting another build, reconnect or artifact fetch. 123ed658c- When
--remote,ios.remoteorandroid.remotenames a Mac, Stim waits up to 45 seconds for that Mac's device offer, where it waited 3 seconds and could refuse a healthy host whose device inventory probe was still running. Automatic placement keeps its 3-second probe. 2b609bb42 - A device wait cancelled by
stim stopno longer starts a reclamation afterwards. 48d99c91d - iOS bundle requests wait for the client lookup before Metro serves them, so a launch is attributed to its workspace instead of racing it. f741720db
- Agent log attribution compares exact device and inode IDs of the log file, read as bigint stats, so a replaced log file is not mistaken for the original. d50226af5 (
@stim-cli/core) - The programmatic API infers a run's result type from the requested platform. d928510ff
- Debug logs (
debug.logs) record iOS app process inspection outcomes, in-flight native stop phases, and hosted worker cancellation paths (@stim-cli/server), without arguments or environment contents. abf9826d7, 4679d436f, 463cd44e0
Docs
- The website and
stim guidecoverremote.easFallback, thelocalvalue, the automatic pool settings,stim macos --plan, the Watchman finding, hosted build phases, port allocation, the macOS app stop, the tutorial, and@stim-cli/ci. - The guides and website say that Desktop shows a launch card before opening a device, and that a reopen event does not take focus from a
stim macoscopy started in the background. f254e20ce, ac4066449
QA
The maintainer approved this release on 2026-10-09 ("npm 1.19.0 + Desktop 0.1.19"), to ship origin/main as it stood. Pre-tag QA is omitted, as with 1.18.0. This deviates from RELEASE.md section 3, which has no waiver for a stable version, and from docs/release-recovery.md, which asks for the full lane on the first release that contains a new package (@stim-cli/ci).
Omitted before the tag, all eight rows of the matrix (node scripts/release-qa-matrix.mjs v1.18.0; 314 changed paths, 106 mapped to a row, 0 unclassified after this release adds rules for packages/ci, .gitattributes and .watchmanconfig): loop, logs, caches, real-repository, remote-provider, release-build, android-flavor and launch-evidence, plus the native suites run-native-e2e.mjs and run-cache-e2e.mjs.
Run before the version commit: format:check, lint, build, typecheck, knip, release:prep --check, CLI version/help/guide, licenses:check in apps/mobile, and pnpm tarball inspection of the seven packages. Not run locally, left to CI: pnpm test, test:e2e, test:runtime and test:compat. Run after push: exact-commit CI, including the full Windows job, and the Release workflow's own gates.
1.18.0
Stim Desktop and the Stim phone app ship through their own workflows; this release covers the six npm packages. Commits that only changed those apps are not listed.
Behaviour changes
- Automatic maintenance is on by default.
maintenance.modedefaults toonwhere it wasreport. A background pass, started by CLI commands andstim status --watchwhen a check is due, now removes whatstim gc --deleteremoves: orphaned workspace directories and stale cache registrations, build outputs of idle workspaces, least recently used build-cache and Metro entries over their caps, the age sweep, the Swift compilation cache (emptied whole, only below the hard free-disk floor), and linked worktrees whose branch is merged or whose pull request was merged or closed. Worktree removal follows gc's finished-worktree rules, including thegc.worktreeGraceMinutesgrace period (default 120 minutes), so a worktree that changed recently is kept; gitignored files do not count as changes, and a worktree with uncommitted or untracked files, unpushed commits, a running dev server, a booted owned device or amaintenance.keep truepin is kept. Every action is logged (stim logs --source maintenance,stim status --json). Turn it off withstim settings set maintenance.mode off, or keep only the plan and the log withstim settings set maintenance.mode report;stim settings set maintenance.removeFinishedWorktrees falseturns off only worktree removal. A scratch or test home (STIM_HOMEset) and CI force itoffunlessSTIM_MAINTENANCEis set, even over a configuredmaintenance.mode. 888d4f45b, b925c4639 stim-server setupapproval prompts default to Yes. Each per-grant question reads[Y/n]. Enter,yandyesapprove;n,noand any other answer decline. Keys typed ahead before the prompt appears are discarded, so an Enter pressed earlier does not approve a grant.--yesand the no-terminal refusal are unchanged. 422f5ba35
New
stimexports a programmatic API.import { createStim } from 'stim'gives a client withrun,stopanddiagnosticsthat call the same lifecycle operations asstim ios|android|macos|web,stim stopandstim logs, in a bundled worker, with typed results,StimErrorfailures andAbortSignalcancellation. See the website's Programmatic API page. d90965a5b- Automatic maintenance acts under
maintenance.mode on. New settings:maintenance.worktreeCheckMinutes(15),maintenance.sweepHours(24),maintenance.olderThanDays(7),maintenance.protectRecentHours(2),maintenance.removeFinishedWorktrees(true), the project settingmaintenance.keep(pins a workspace so a pass never clears its outputs or removes its worktree) andcaches.ccacheMaxGb(5).stim status --jsonmaintenance data gainsworktreeandsweepentries inlastChecks, and plan actions carrydir,olderThanDaysandcheck;lastPass.modecan beonwith the bytes freed.stim statusandstim gcprint an "Auto maintenance (on)" line with the action count and space freed. Disk-driven steps stop once free space is back above the floor. b925c4639 --planworks withios.remoteorandroid.remoteset toautoor a Mac name. Instead of refusing withSTIM_BAD_ARG, the plan asks that Mac for its device (a read-only probe, nothing reserved) and reads the architecture or ABI from it. Withautoit repeats the placement decision a run would make now.--jsongainsplacement, for exampleon janics-mac-miniorthis Mac; auto may use janics-mac-mini, and the plain output prints aplacementline.eas,proxyand a Mac that does not answer still refuse withSTIM_BAD_ARG. e0edecf04build.offerreports memory (@stim-cli/server,@stim-cli/core). The capacity block gainsmemoryUsedBytes(Activity Monitor's Memory Used) andmemoryTotalBytes; they are omitted when the worker cannot read its memory, and absent from an older stim-server.stim doctor --jsoncarries them for each remote Mac. 2a6af4023stim macosbuilds record their launch time and compile step count.environments[].builds.macosinstim status --jsonincludeslaunchinphasesonce the launch step finishes, andcompileSteps, SwiftPM's step total. 494cd2d54stim doctor --jsongives the finding for a directory that is not a React Native or Expo app the codenot-an-app. c9b8f12ce
Fixes
stim worktree warmmoves a carriedios/Podsto the worktree's path, so the firststim iosskipspod install. WhenPodfile.lockand the carriedPods/Manifest.lockdiffer only by checksums of podspecs that embed the source checkout's path (the precompiled ExpoModulesCore), warm rewrites the source path inTarget Support Files,Pods.xcodeproj,Local Podspecsand absolute symlinks and makesManifest.lockmatch. When the locks already match but Pods still name the source path, warm applies the same rewrite. If carriednode_modulesdo not match the worktree's lockfile, another file still names the source path, or the move fails,stim iosstill runspod install: when the locks matched, warm deletesPods/Manifest.lockto force it, and when they differed, the differingManifest.lockstays. Warm never editsPodfile.lock. 82ee9c46c, 33797014cstim ioslists simulators once for the budget gate, the capacity check and device setup, and lists them again only after a budget reclaim, instead of once for each. a76467c6e- The CocoaPods version that
stim doctor, a build offload andbuild.offercompare is read in the project's Ruby environment (its.ruby-version, with a UTF-8 locale), the onepod installuses, instead of the machine's default Ruby.build.offertakes an optionalrubyVersion, and the worker caches its toolchain report for 60 seconds per Ruby version. 05ebc115d - stim-server (
@stim-cli/server) answers a client'shellowith its host health probe running alongside the identity check, not after it, and readstailscale statuswithout blocking the event loop inserve,pairandservice. 93a418c2d - The hosted relay waits 20 seconds, where it waited 10, for a hosted Mac to answer a request. When it does not, the error reads "the host was busy or slow: it did not answer
METHODwithin 20 seconds", whereMETHODis the request's method name. 93a418c2d - Phone push notifications are quieter. stim-server notifies "Agent stopped" once per workspace per run, where a run ends when the workspace goes idle, and notifies at most one needs-attention item per workspace per run, which starts over when the workspace stops being idle or has no item left. A pull request that became ready or merged keeps its own notification. In the phone app, Work started defaults to Off. 30195c49e
Docs
- The website and
stim guidecovermaintenance.mode, the programmatic API, the--planplacement field,build.offermemory fields, thestim macosstatus fields, and warm's Pods move. stim guide tutorialstep titles and the website use title case for Desktop and phone screen names (Agent Actions, App Logs, Live View and Control). 1cd98eaae
QA
Pre-tag QA is omitted by the maintainer's explicit decision on 2026-10-08 ("release now"). The maintainer QAs right after the release, as with 1.17.2. This deviates from RELEASE.md section 3, which has no waiver for a stable version.
Omitted before the tag, all eight rows of the matrix (node scripts/release-qa-matrix.mjs v1.17.2 --until 502eb0f7b; 338 changed paths, 75 mapped to a row, 0 unclassified): loop, logs, caches, real-repository, remote-provider, release-build, android-flavor and launch-evidence, plus the native suites run-native-e2e.mjs and run-cache-e2e.mjs.
Run before the version commit: format:check, lint, build, typecheck, knip, release:prep --check, CLI version/help/guide, licenses:check in apps/mobile, and pnpm tarball inspection of the six packages. Not run locally, left to CI by the maintainer's decision of 2026-10-08: pnpm test, test:e2e, test:runtime and test:compat. Run after push: exact-commit CI, including the full Windows job, and the Release workflow's own gates.
1.17.2
Stim Desktop and the Stim phone app ship through their own workflows; this release covers the six npm packages. Commits that only changed those apps are not listed.
New
stim-server --loopback-onlylistens on127.0.0.1only and refuses any request that names a remote peer (X-Forwarded-For, astailscale serveadds), so phones and other Macs cannot connect./healthreportsloopbackOnly: true, and the flag applies only to serving, not to subcommands. The next Stim Desktop release starts its own server with it and requires stim-server 1.17.2. 5dbd21e9cstim doctor --jsonreports alapsedstate andexpiresAtfor build and device-host requests to remote Macs. Apendingentry carriesexpiresAt, when the request lapses;lapsedmeans the request expired before approval, whilerevokedmeans the remote Mac refused or revoked this Mac.stim doctor --fixasks again on a lapsed request. 55a59ddce
Fixes
stim-server setuptells apart Tailscale not installed (a download link), the Tailscale app turned off (turn it on from the menu bar icon), and a CLI-only install that is logged out (tailscale up), where it used to say only "Start Tailscale on this Mac". 2db7176c9stim-server service update --from <dir>installs the.tgzpackages of a client's build. It no longer asks the registry to vouch for Stim's own packages, so an unpublished version installs, and it still checks the registry signature of every dependency they bring from npm. 55a59ddce- When
npm audit signaturesfails, the error includes npm's own text: its JSON error summary, or its stderr after "did not print a JSON report:". 55a59ddce stim doctornames a tool that is missing on one side of a toolchain comparison, such as "no CocoaPods there, CocoaPods 1.16.2 here", where it printed "CocoaPods null there". 55a59ddce- The hosted iOS and Android device status validators in
@stim-cli/coreaccept a status without a placementreason. 27b518a23
Docs
- CLI text,
stim guidetopics and the website call a running workspace "active" where they said "live", including thestim statusmemory line. 116af6ee8 - The website's remote Macs and settings pages and
stim guidecover thelapsedstate,expiresAtandservice update --from. 55a59ddce - The website's remote Macs page describes the three Tailscale answers of
stim-server setup. 2db7176c9 stim guide settingsnames the Desktop path "Remote Macs > Running here" where it said "Phones > Hosted here". 20333b70e
QA
Pre-tag QA is omitted by the maintainer's explicit decision on 2026-10-08 ("Lets release before qa actually"). The maintainer QAs the Remote Mac pairing flow right after the release. This deviates from RELEASE.md section 3, which has no waiver for a stable version.
Omitted before the tag, all eight rows of the matrix (node scripts/release-qa-matrix.mjs v1.17.1 --until <head>; 149 changed paths, 18 mapped to a row, 0 unclassified): loop, logs, caches, real-repository, remote-provider, release-build, android-flavor and launch-evidence, plus the native suites run-native-e2e.mjs and run-cache-e2e.mjs.
Run before the version commit: format:check, lint, build, typecheck, knip, pnpm test, test:e2e (99 passed), test:runtime, pnpm run test:compat install-from.compat.test.ts (passed; covers service update --from), CLI version/help/guide, and pnpm tarball inspection of the six packages. The rest of test:compat (iOS device and other native tool calls) was not run. Run after push: exact-commit CI, including the full Windows job, and the Release workflow's own gates.
1.17.1
Stim Desktop and the Stim phone app ship through their own workflows; this release covers the six npm packages. Commits that only changed those apps are not listed.
Removed (breaking)
Build machines and hosting machines are now remote Macs. Settings, flags and environment variables are renamed with no migration and no aliases: the old names are no longer read. 59f8172db, adca393d4
| Before | Now |
|---|---|
offload.machines and hosting.machines | remote.machines (one list for build and device-host approvals) |
offload.machine | remote.build |
--build-machine (ios, android, macos) | --remote-build |
STIM_OFFLOAD_MACHINE | STIM_REMOTE_BUILD |
offload.mode | remote.buildMode |
STIM_OFFLOAD_MODE | STIM_REMOTE_BUILD_MODE |
offload.workerRoot | server.workerRoot |
offload.gradleDaemonIdleMinutes | server.gradleDaemonIdleMinutes |
offload.maxLoadPerCore | server.maxLoadPerCore |
stim doctor --json buildMachines | remoteMachines |
doctor --fix now asks each machine in remote.machines for build and device-host approval, and drops a device-host finding whose title a build finding already reports.
The plain-text build summary of stim ios and stim android now reads "build placement" where it read "build machine".
Behavior changes
ios.remoteandandroid.remoteare allowed at machine scope, so one setting on this Mac applies to every project. bff943280
New
stim macosbuilds report phases and progress.status --jsonshows the build asenvironments[].buildwith platformmacos: phasesprepare,compile,installandlaunch, and duringcompileSwiftPM's[done / total]asdetail.unit: "steps". Finished runs and their phase times are inenvironments[].builds.macos, and macOS builds are labeled in machine usage. 8c36483a0stim-server setupprints concise output. It shows one running line per step and the result, and--verboseprints the full step detail. The running line is cleared before an error is printed. 6b21927d7stim gc --delete --cache archived-logs:<id>,archived-recordings:<id>andarchived-agent:<id>clear one kind of data from one archive and keep the archive's record. 449c76fa4
Fixes
stim-server setupreads the host's Screen Recording and Accessibility permissions from the running service, so it no longer asks again for a permission that is already granted. 814a621f3stim-server setupcaptures the login shell's environment in its own session, so an interactive shell no longer takes over the terminal's foreground process group and stops the command with SIGTTOU. 6b21927d7- The hosted agent driver's error includes the agent-device proxy's stderr when the proxy exits before it is ready. 1e0ba5276
- The full Windows unit suite stays green: server and worktree-refresh regression tests are synchronized. Test-only change. 71a41d613
Docs
stim guidetopics,packages/server/README.mdand the website pages follow the remote Mac rename,--verboseand the macOS build phases. 59f8172db, 6b21927d7, 8c36483a0
Migration notes
- Rename every setting, flag and environment variable in the table above in your project config, machine config, scripts and CI. Nothing reads the old names.
- Merge
hosting.machinesintoremote.machines. Runstim doctor --fixto request build and device-host approval from each listed Mac. - Move
offload.workerRoot,offload.gradleDaemonIdleMinutesandoffload.maxLoadPerCoretoserver.*on the Mac that runs stim-server. - Tools that read
stim doctor --jsonmust readremoteMachinesinstead ofbuildMachines.
QA
Pre-tag QA waived by the maintainer's coordinator on 2026-10-07. Not run before the version commit: the section 3 native and manual QA matrix, and test:compat (native compatibility). Run before the version commit: the section 2 pre-flight apart from test:compat, and pnpm tarball inspection. Run after push: exact-release-commit CI, including the full Windows job dispatched on the release branch, and the Release workflow's own gates.
1.17.0
Stim Desktop and the Stim phone app ship through their own workflows; this release covers the six npm packages. Commits that only changed those apps are not listed.
Behavior changes
devices.idleShutdownMinutesnow defaults to 30 (it was0, never). The workspace supervisor shuts down an owned simulator or emulator that has been idle for 30 minutes. Set it to0to keep the old behavior. a783d296c- New
devices.reclaimIdleMinutessetting, default 10. A run that is waiting for a device slot reclaims the slot of an owned device that has been idle that long, shutting that device down.0never reclaims. ff7831db6 concurrency.maxDevicesqueues instead of refusing. At the cap,stim iosandstim androidon owned devices now wait for a slot in first-come order across the Stim home (600 seconds by default).--wait <seconds>bounds the wait, and--no-wait(or--wait 0) refuses at once as before. A workspace's own live device skips the queue, remote targets do not join it, andstim stopcancels a queued run. 947ffea70--remote autois no longer refused.stim ios --remote autoandstim android --remote auto(andios.remote/android.remoteset toauto) run on an approved hosting Mac when this Mac is full or busy, and locally otherwise.offload.saturationLoadis also the busy threshold for this placement. 4e298d801worktree removeguardrails. It refuses, even with--force, a folder astim-server serviceruns from (STIM_WORKTREE_SERVICE); reinstall or uninstall the service first. Run from a subfolder of a source checkout, it reclaims only the project at or above that folder and the projects nested under it, and refuses when none is registered there. e6a057d20, a3baff816- Hosted devices are parked. A hosting Mac parks a client's iOS simulator or Android emulator on stop, within
pool.iosParkedMaxandpool.androidParkedMax, and the same client adopts it on its next run. Another client never adopts it. Before this release a stopped hosted device was deleted. 96244088f android.remoteaccepts the same values asios.remote:eas,proxy,autoor a tailnet machine name. 4e298d801
New
Hosted Android
stim android --remote <machine>runs an owned Android emulator on an approved hosting Mac, as the iOS and macOS hosting paths do. ad373a6d9- Hosted Android uploads only the files that changed, hands over an Android build the same Mac ran for the client, and pulls logcat back for
stim logs. 1936627c3 - Agents drive a hosted Android emulator with agent-device, and Desktop and the phone app view and control it through stim-server's relay. ecc07f158, e51dccf1f
- A host counts its own devices in hosted admission. d3099010f
Hosted parking and the hosting Mac
- Hosted simulators and emulators are parked on stop and adopted by the same client; a parked device keeps its upload store, so adoption uploads only changed files.
stim gclists and deletes parked hosted devices. 96244088f, 43395b64a, ef79fce38 - stim-server lists the hosted sessions on the hosting Mac and lets that Mac's person stop them. f3bfff5e2
Device slots and capacity
status --jsoncarriesbuild.waitingForfor build and device slot waits, andstatsrecordscapacityWaitsanddeviceSlotWaitMs. 947ffea70statsandstatusrecord device capacity refusals, so Desktop can suggest a hosted simulator when the device limit is hit. d9718cfc6- Concurrent boots hold
concurrency.maxDevices: the count and the boot record share one lock, so parallel worktrees no longer exceed the cap. de96761b5
Setup, guides and status
stim guide tutorialteaches the first run in a sample app, andstatusreports the tutorial marker. b4f24c6ae, e661190d4stim-server setup(shipped in 1.16.0) is unchanged in the packages. Desktop's Add build machine wizard and Discovery prompts, which ship with Stim Desktop, build on it, andstim guide settingstells agents they never runstim-server setupor editoffload.*themselves.- Native macOS agent actions show in
stim logs. a6958a216
Archives
- stim-server serves an archived workspace's build history and recordings. 2ee0d97e2
Fixes
- Hosted Android logs stop following exited app PIDs. 7254a46cf
- A skipped cache lookup no longer reports an outcome in
status. 32700c487 - Android CAS wrappers run under the Node Stim runs on. 13ab59ac6
- Archives of gone worktrees keep their branch and pull request, and a source-checkout or non-git reclaim archives the workspaces it removes. 699de22f5, b0c933929
- Teardown reports agent-device's own refusal when it cannot list sessions. 80a69c19f
- Compilation-cache prefix mappings are quoted for paths with spaces. 93b44e092
- Simulators are listed once between device setup and the boot check, which shortens
stim ios. 40c236f04 - The full Windows unit suite passes again. The Windows Xcode mapping expectations are corrected, the kept-agent fixture waits for its child to exit, and hosted Android and process-group tests that Windows cannot run (POSIX fixtures, process-group signaling) are skipped there. Test-only changes. c5ca280b3, 53a6045a8, c7b464d3f
Docs
website/docs/commands.md,settings.md,owned-devices.md,remote-machines.md,worktrees.mdand the phone app pages follow the changes above. 947ffea70, 84b6b8719, 49e9e5ea5
Migration notes
- If you rely on owned devices staying booted for more than 30 idle minutes, set
devices.idleShutdownMinutesto a larger value, or0. - A run that waits for a device slot may now shut down another workspace's owned device that has been idle for
devices.reclaimIdleMinutes. Set it to0to disable. - Scripts that expected
stim iosorstim androidto fail immediately at theconcurrency.maxDevicescap should pass--no-wait.
QA
Pre-tag QA waived by the maintainer on 2026-10-07 (asked whether to release with no QA beyond CI, as for 1.16.0, the maintainer answered "Lets do it"). Not run before the version commit: the section 3 native and manual QA matrix, and test:compat (native compatibility). Run before the version commit: the full section 2 pre-flight apart from test:compat (format:check, lint, build, typecheck, knip, unit tests, test:e2e, test:runtime, CLI help, guide and version), and pnpm tarball inspection. Run after push: exact-release-commit CI, including the full Windows job dispatched on the release branch, and the Release workflow's own gates.
1.16.0
Stim Desktop and the Stim phone app ship through their own workflows; this release covers the six npm packages. Commits that only changed those apps are not listed.
New
Remote device hosting
stim ios --remote <machine>runs an owned iOS simulator on an approved hosting Mac, asstim macos --remoteand the Android hosting path already do. It uploads only the app files that changed, hands over an iOS build the same Mac ran for the client, and pulls the hosted app's native logs back forstim logs. 83ad52f, 785a19f- Desktop and the phone app view and control a hosted iOS simulator through stim-server's relay. fae6cd2
- Agents drive a hosted iOS simulator with agent-device. 14f1715
Setting up stim-server
stim-server setupwalks a Mac through becoming a stim-server host. stim-server issues setup tickets on access requests and serves a node-bound setup journal route;stim doctor --fixsendsSTIM_ACCESS_TICKETwith its access requests, accepts only server-format tickets, and reports the host's permissions. e958eea, cf587dd, 034a6da
Workspaces, status and maintenance
stim worktree removekeeps a removed workspace as an archived workspace, and stim-server serves its archived logs, agent actions and replays.gchas archived scopes. ddd8543, 9c05416- A report-only automatic maintenance engine in the CLI. b4c9926
stats,gcand Desktop Storage report agent-device and SwiftPM cache disk use. daeafa5statusreportsagentDevice.stateDir, and agent-device state lives in the workspace. 44e7875status --jsoncarries completed step durations and cache hit or miss for a build. af5f3de- Run placements record
slotWaitMs, anddoctoradds a note on macOS when a Mac is a candidate for offloading builds. aa848119
Fixes
stim iosandstim androidapprove every URL scheme the app declares on owned simulators. ac39538stim-server service installwaits for the old stim-server to release its port and fails when the new job exits. 3fa960b- Hosted devices are deleted when a hosted session stops, and stopped sessions are retired at server start. 85612bb
- Old compiled helper builds are pruned. 5bd3bdc
@stim-cli/coreaccepts Windows absolute paths in a hosted macOSremoteConfig; it rejected them before. The full Windows unit suite passes again. f87580astatusshows every platform an app supports. 9244fe5
Docs
website/docs/commands.md,settings.mdandowned-devices.mdfollow the changes above. af5f3de, 3fa960b, 85612bb
QA
Pre-tag QA waived by the maintainer on 2026-10-06 ("Publish it, no qa needed can do regular release"). Not run before the version commit: the section 3 native and manual QA matrix, and test:compat (native compatibility). Run before the version commit: the full section 2 pre-flight (format:check, lint, build, typecheck, knip, unit tests, test:e2e, test:runtime, CLI help, guide and version), and pnpm tarball inspection. Run after push: exact-release-commit CI and the Release workflow's own gates.
1.15.0
Stim Desktop and the Stim phone app ship through their own workflows; this release covers the six npm packages. Commits that only changed those apps are not listed.
New
Builds on another Mac
- Offload builds to a paired build machine.
stim iosplaces simulator Debug builds,stim androidemulator debug builds andstim macosSwiftPM Debug builds on a Mac that has granted this one build access, and installs the returned artifact under the post-mutation cache key. Any failure builds locally.offload.modeisauto(build here while a build slot is free and the load is underoffload.maxLoadPerCore, otherwise offload),forceoroff. e433902, c5f3514, 4c6542d stim-serverhas abuildcapability, granted only on the worker, per client, by approving a build request withstim-server devices grant <id> --build. A client names its build machines inoffload.machines,doctor --fixrequests access and pins the worker's tailnet node, and the token goes only to that node. A build client never getsreadorcontrol, and every method excepthellonow needsread. eeadd14- Placement is load-aware (
offload.maxLoadPerCore), reports the machine's capacity indoctor, and records where each compiling build ran and why instatus, build history andstats.offload.machine, or--build-machineonios,androidandmacos, names the one machine to require, with no local fallback, orlocalto always build here. A worker holds one of its own build slots for the whole offloaded build, and a client moves to the next machine when one refuses or fails to sync. d62bc69, 0522c97, eb41678, fc27bae - An offloaded build survives a dropped connection: the worker keeps the job for 5 minutes and the client reconnects for up to 3 minutes to reattach before building locally. The Gradle daemon stays warm on the worker for
offload.gradleDaemonIdleMinutes(default 30).offload.workerRootmoves the worker's checkouts. 4ad0f8a, ad307fc stim-server service install|status|uninstallmanages a per-user LaunchAgent for stim-server,service update --release <version>|--from <dir>andservice rollbackswitch builds, and an approved Mac can update the service over the tailnet. On a hosting Mac the service runs under a signed and notarized Stim Host app that owns its macOS permissions;installdownloads the pinned release, checks its digest and team signature, and installs~/Applications/Stim Host.app. a6a8746, 8710cf9, 1bf3791, 58ae393, 387b202, 28b3566
Native macOS apps
stim macosbuilds an owned Swift Package executable in Debug, launches an isolated development bundle signed ad hoc, and shows its window in Stim Desktop and the phone app.macos.productandmacos.infoPlistselect the product and development plist,macos.argumentspasses launch arguments, andmacos.resourcesandmacos.assetCatalogstage resources and a compiled asset catalog before signing. It refuses withSTIM_BAD_ARGwhenmacos.productis unset. 038a8ed, 3fd1587, 9f9b4d0, 1bf6e18stim macos --remote <machine>runs the app on an approved hosting Mac named inhosting.machines, streams and controls it from Desktop and the phone, copies its logs to the client forstim logs, and reports a session the host stopped instim status. Viewing and Control follow the app's front window or a pinned window. An offloaded macOS build is handed to the hosted session on the same Mac. 9a2b135, 3db30ee, 316ee42, 8341d3d, 422a3a1, 3c2ff56, 6e4e82c- Agents drive a hosted macOS app through agent-device
macos-appleases; the adapter starts only when the daemon advertises that backend, andSTIM_AGENT_DEVICE_BINnames the agent-device binary. 38da60b - Workspace GC reclaims owned macOS Swift build outputs. 5465786
Remote device hosting
- A Mac can host owned iOS simulator and Android emulator sessions for another approved Mac. Hosting is approved separately from build access, configured with
hosting.machines, and reserves, installs and launches the app, bridges a development app to the client's private Metro, and streams and controls the session. f402f4e, c3ea73b, 116cc4d, 52b1fa1, d1fa435, 1a17b08, be09108 metro.tunnelaccepts a tailnet-onlytailscaleoption. da7eaf4
Watching and recording devices
stim-serverrecords device screens for replay and serves range, markers, seek and live replay to clients.recording.enabled(defaulttrue) turns it off and deletes the footage, andgccleans it. 74cfc23, a752880stim-serverstreams a USB-cabled physical iPhone (view only), and streams and controls a leased physical Android phone over adb with a vendored scrcpy server, and the owned Chrome page fromstim webas platformweb. 2d794fd, eddd653, cd0fba9, 1166314stim webrecords an agent's clicks, typing, key presses and scrolls on the owned page asagent_actionrecords and tracks in-app route changes. 1abc436stim-serverserves read-only workspace diffs andmachine.details(thegcdry run, stats and build machine state) to paired phones. dba7f7c, d0e46c4
Status and notifications
stim status --jsonreports more:stage,appPresence,gitChipand the cause of a failed run; disk use,metro.bundle,build.detailandbuild.missReasonfor a running build, each worktree's pull request, and 10-minute CPU and memory history;warmingandreadyphases; each workspace's measured physical-footprint memory; the coding-agent sessions working in it (ended sessions stay for 3 days); leased physical devices; the owned page's target and DevTools drivers; andactivity.recent. Most fields are optional. 666651e, a268816, 9aac0cc, 6e6d663, 9c845b7, 62868fe, 09133a1, 01a80d5stim-serverpushes what a person overseeing agents needs:started,stuck,looping,finished,machineandcontrolnotifications, each at the level the phone chose (Alert, Silent or Off), with a notification history served to phones. A build that waits names the workspace it waits on. 0d87710, 786397a, 0584287, 8105f25, add11aeworktree warm,start,ios,androidandwebprint a Stim Desktop link to the workspace on stderr when Desktop is installed, and add it to--jsonaslinks.desktop. e00c0fa
Devices, settings and cleanup
metro.portpins a workspace's Metro port. 7a41544devices.idleShutdownMinutes(default 0, off) shuts down idle owned simulators and emulators. 27dc326- A parked simulator or emulator keeps its apps and data. Adoption removes other third-party apps and clears the adopting app's data, and skips the install after verifying the installed bytes.
gc --cache parked --deleteerases parked devices. 057d906 ios.deviceType,ios.runtimeandandroid.systemImageread the machine settings layer. An uninstalled model or runtime names the layer it came from. ef88892stim ios --device-typeworks on the EAS remote backend. 6e345dcgcreports and reclaims idle watchman, Gradle and Kotlin daemon memory, reports which caches a scopedgc --deletewould empty, and tells caches that share a name apart. 8224491, 8cdf499, 87f7788statusdetects automation tools from one registry and adds Argent. 1d8be14
Fixes
stimandstim-serverrefuse to start on a Node older than theenginesfloor, exiting 1 withSTIM_NODE_UNSUPPORTED, instead of crashing mid-command when a version manager picks a project's older Node. d8a77e5- Owned AVDs are created and deleted in the root the emulator reads.
avdmanagerignoredANDROID_AVD_HOMEwhen the directory was missing, and deleted fromXDG_CONFIG_HOME. 3549cea - An owned AVD's console queries get at least 1 second each, so a slow earlier call no longer reports a running emulator as not detected. An adopted emulator gets the 60 second bundle window of a new one. da5dddb, 67e2992
- A Release JS swap refreshes the
expo-updatesembedded manifest id and commit time on iOS and Android, and recompiles the React Native asset catalog on iOS. Before, an update downloaded after a swapped build launched instead of the injected bundle. 60c5082, faabad4 stim ios --remotebuilds one simulator arch. 9c93b98stim androidrestores a lost Metroadb reverseon reload and reports it instatus. faf34bastim reloadonwebreports the page it reached, not the launch URL. fd7a59c- A slot's Metro errors stay visible in
logs --errorsafter another slot relaunches. ANSI escape codes are stripped from captured Metro messages. 0b2ef4e, 3fe8690 logs --followends when its reader is gone. 7741824- On iOS, adoption keeps the Expo dev menu off after it clears a dev client's data, skips redundant dev-client simulator writes, and carries scheme approvals through park and adoption, which saves about 4 seconds per adoption. e0bd29b, f71f89d, 644a166, aa86906
worktree warmskips.jj, andworktree removeignores watchman cookies when it checks for dirty work. 60c4505, 5a4edb6gcno longer fails withE2BIGcomputing merge state on a branch thousands of commits ahead, prunes status cache entries of removed worktrees, and records a stopped Metro's watchman root removal. a4f8486, c34ecf0, ab70e90statusaccepts a pull request opened from the checkout's own fork and reports self-healing device states and a port another app holds on an idle workspace as info notes. fab89dc, 4312601, 9c71372stim android --system-imageand--device-profilerefuse withSTIM_BAD_ARGon a remote run, where the remote backend picks its own device. cd798b5- A build that waits for a device reports the wait as its own phase, and build estimates count one run once. c29950a, d6c31e0
- stim-server keeps re-checking Tailscale after start, stays up when pruning footage fails, and listens before it reads the Stim home. ee9e595, cfdba80, 14028db
Docs
stim guide agentmakes the worktree workflow the default and tells the agent to share the Desktop link once. New website pages cover native macOS apps, and the commands, settings, owned devices and Desktop pages describe the features above. 81a652a, 038a8ed
Migration notes
- stim-server pushes changed. The failed-build, log-error, low-disk, stopped-app and slow-build pushes of 1.14.0 are replaced by
started,stuck,looping,finished,machineandcontrolnotifications, and every category starts Silent, so a paired phone gets no alert until it opts in. - A
stim-serverclient that holds onlybuildcan no longer read status: every method excepthelloneedsread, and a build client never getsreadorcontrol. stimandstim-serverexit 1 withSTIM_NODE_UNSUPPORTEDon a Node below 22.12.0. Run them under a supported Node, for example withnvm exec.- A single-arch iOS simulator build is cached under a key that ends in its arch, in the CLI and in
@stim-cli/expo-build-cache. The first iOS simulator build after upgrading misses an entry stored by 1.14.0. - A parked simulator or emulator keeps its apps, and adoption removes other third-party apps and clears the adopting app's data. Before, parking erased the device.
QA
The maintainer waived the pre-tag native and manual QA matrix for 1.15.0 on 2026-10-05. This release uses the full local pre-flight and no matrix rows.
Passed on this candidate (the six package.json files at 1.15.0, on be2bc07):
pnpm install --frozen-lockfile,format:check,lint,build,typecheckandknip.pnpm test: 237 files, 6740 tests passed, 18 skipped.pnpm run test:e2e: 99 tests passed.pnpm run test:runtime, run under Node 22.22.2. The Node 22.12.0 floor job runs in CI.pnpm run test:compat: 5 files, 26 tests passed with the Old iPhone connected.release:prep --check,stim --help,stim guide agent,stim --versionandstim-server --versionat 1.15.0.- Tarball inspection of all six packages: each carries its README and LICENSE, every version is 1.15.0, and no
workspace:range survives the pnpm pack.
Rows of node scripts/release-qa-matrix.mjs v1.14.0 that were not run (all eight were required):
loop(expo and bare on ios and android): one variant was started,expo-ios. It built, installed and launched the app and verified a bundle request from its Metro, then failed during later assertions withcould not inspect ps: spawnSync ps ETIMEDOUTwhile the host load average was above 140. It was not retried. The other three variants were not run.caches(ios and android, both Metro modes): not run.logs,release-buildandlaunch-evidence(ios and android): not run.real-repository,remote-providerandandroid-flavor: not run.
The mapping also reports five paths it does not classify: .worktreeexclude and four files under packaging/store/.
Merged after the version commit and covered by the exact-release-commit CI only: the full Windows unit suite fixes with path-containment hardening (#2586), the stim macos --host to --remote rename (#2594), and two Stim Desktop fixes (#2588, #2596).
Retained after push: exact-release-commit CI, including the full Windows job.
1.14.0
New
@stim-cli/serverpushes attention notifications to paired phones through the Expo push service: a failed build, new log errors, low disk, a stopped app and a slow build. A phone registers an Expo push token withpush.registerand removes it withpush.unregisteror by revoking its pairing. Pushes follow the phone's attention rules: once per occurrence, log errors after the count holds, at most 20 an hour per phone, and more than three at once as one summary. What is already wrong when the server starts or a phone registers does not push. Each push carries the workspace title or Mac name, a short reason and the workspace path, never logs. The Stim phone app, shipped separately, registers for them. 5ff39e2, acb19bd- A Metro stop records its cause. The
supervisor_stoppedlog line carries astopfield andstatusreportsmetro.lastStop: the Stim command that stopped it (stim stop,worktree remove,gc, a budget reclaim naming the reclaiming workspace,start --reset-cache), a signal no Stim command requested, or the dev server's own exit. A supervisor that vanished without recording anything, usually after SIGKILL, a crash or a restart, is logged assupervisor_vanishedby the next supervisor orstim stop. 3e69cf4 stim stop --slot webcloses only the workspace's owned Chrome fromstim weband keeps its profile, Metro, devices and port. 2fed395
Fixes
- Launch verification works in a workspace with named device slots. A Metro bundle delivery now counts for the slot it can be attributed to: an iOS simulator's request is matched to the launched process through
lsof(clientPidon thebundle_response_*records), and an Android or physical-device delivery counts while no other slot of the same platform is running. Before, every delivery was ignored once any slot existed, so Android reportedUNVERIFIEDand an iOS slotbundlingafter its bundle loaded. When a running sibling slot makes a delivery unattributable, the launch staysunverifiedand names the device andstim logs --slot <slot> --source device. 76f2bd5 stim iosandstim androidrestart an app that is already running, like Xcode's Run, withsimctl terminateon a simulator andam force-stopon an emulator or--devicephone. A repeat run with an unchanged install no longer endslaunch UNVERIFIEDbecause the old process kept running and requested no bundle. The launch line readsrestarted running app (was pid N). 175fbedstatus --watch --jsonexits when its reader is gone, instead of running on, with its watchers andadb track-deviceschild, on a quiet workspace that never wrote again. 6cf7843stim stop --slot <name>refuses withSTIM_BAD_ARGand lists the workspace's slots when it has no such slot, instead of printingStopped: port keptand exiting 0. 2fed395- With
web.viewportatdesktop, thestim webpage is 1280x800 as documented. Chrome's own UI had made it 1280x713. e24bf01 - The
--device-typeand--runtimemismatch refusals carry their real remedy in--json: reap the simulator withstim worktree removeorstim gc --delete, or pass--slot <name>to create the requested one beside it. The remedy said to runstim doctor. d70788f stim ios --device-typeand--runtimerefuse withSTIM_BAD_ARGon a remote run (--remoteorios.remote), where the remote backend picks its own device, instead of being silently ignored. Theios.deviceTypeandios.runtimesettings do not refuse there. 07d41fbstim ports getandstim webno longer printPort N already in use, trying next...for each busy port they skip. 67bdb61
Migration notes
webis refused as a device slot name byios,android,device lock|unlockandlogs, becausestop --slot webnow means the owned Chrome. A device slot already recorded aswebstill loads, but only plainstim stoporworktree removereach it.stim stop --slot <name>with a slot the workspace has not recorded now fails withSTIM_BAD_ARGand stops nothing.stim iosandstim androidrestart an app that is already running, so in-app state from the previous session does not survive a run. If stopping the running app fails, the launch is refused.status --watch --jsonexits when the reader of its stdout goes away. On Linux, a detached watch whose stdout is a pipe or socket, such asstim status --watch --json | cat &, also exits when the shell that started it exits.stim ios --remotewith--device-typeor--runtimenow refuses. Drop the flags for a remote run.- The
--device-typemismatch refusal now also offers--slot <name>, and the remedy text is on the remedy line instead of in the message. - While a phone is registered for push, stim-server keeps its
stim status --watch --jsonchild running with no client connected and reads the disks every minute.
QA
No-QA stable release explicitly authorized by the maintainer on 2026-09-26,
following the 1.10.1 through 1.13.0 precedent.
Omitted locally before the version commit: format:check, lint, typecheck, knip,
unit tests, test:e2e, test:runtime, test:compat, and the pre-tag native/manual
matrix. Retained before the version commit: candidate build, CLI and server version,
CLI help, guide agent, web --help, and tarball inspection of all six packages. Retained after push:
exact-release-commit CI.
Each included change merged with passing CI. Pull requests record their own real-tool checks.
1.13.0
New
stim webopens the workspace's web app in a Stim-owned headless Chrome with its own profile. The page's console, failed requests and browser errors go to the workspace logs,launchedreports whether the page loaded,statuslists the page, andstim reload web,stop,gcandworktree removereload or tear it down. Theweb.url,web.ignoreCertificateErrorsandweb.viewportsettings choose the page, accept a dev server's self-signed certificate in the owned profile, and pick a desktop or phone viewport.stim webnever starts a dev server: start Metro withstim start, or your own server on a port fromstim ports get, first. With nothing serving the page it reportslaunched: "unverified"and prints the command that starts one.stim guide webdocuments it. 126da43, b101f65, 881e34b, 3b99318, a938f5a- In a monorepo, a web package that depends on neither
react-nativenorexpo, such as a Vite app inapps/web, resolves to the one Stim app registered in the same Git worktree.ports,web,settings,logs,reload,stopandstatusrun from that package act on the app's workspace and name it on stderr. 2926912, 78dbd40 - A failed page load names its cause: a self-signed certificate, an
https://URL on a plain HTTP server or the reverse, with thestim settingscommand that fixes it, or a page that did not finish (20 seconds with no answer, 60 once a server other than Metro answered). Page failures appear inlogs --errors. cead9a9 logs --errorsand the status error count report the page's errors from its latest load only:stim web,stim reload, or a reload or navigation the page makes itself. A page load does not hide the native app's errors, and an iOS or Android launch does not hide the page's. 0a510f0
- In a monorepo, a web package that depends on neither
gcremoves linked worktrees whose pull request was merged or closed, checked withgh pr list, in addition to worktrees whose branch is merged.stim worktree removeaccepts a merged pull request whose head is or contains HEAD.gc --jsonreportspullRequestandpullRequestUnknownfor each linked worktree. 2c552c1status --jsonlists each platform's last 10 runs inbuilds.<platform>, with their result, slot, configuration, cache key, cache outcome and phase timings. 325bf8d- A failed build whose compiler reported errors records up to five
diagnostics(file,line,column,message) inlastBuildsand the run result. 1b7f43f statusreports whether the workspace's app process runs on each owned booted simulator or emulator, asapp: { id, state }in--jsonand<id> runningor<id> not runningon the device line. f19e135status --jsonhas a top-levelmachinesection that attributes current CPU and resident memory to one owner per process: each booted simulator and emulator, each workspace's Metro, build andstim webChrome, stim-server, and shared machine-wide services.status --watch --jsonrereads it every 15 seconds while something runs. An environment'smemoryMbstays a fixed estimate. 227d6afgc --jsonlists what--deleteand--idledid to each entry inresults, and a dry run lists every simulator, AVD, iOS runtime and Android system image on the machine, with its owner and the command that removes it, ininventory. Stim never runs those commands. e5c2f9e, 216e014, 762c0a2gcreports each workspace's log size, andgc --deletetrims a Metro, client or device log over 16 MiB, written before the rotation cap, to its newest 8 MiB in workspaces not in use. 68507cagclistscreated-devices.jsonsimulators that simctl no longer shows and AVD names with no registration or data in any AVD root, andgc --deleteforgets them after checking again under the ledger lock. It also sizes orphaned and stale simulators. d46f51b, d664739, d5b05d0stim doctor --platform androidreports a missing Android SDK. When the SDK is found atANDROID_HOME,ANDROID_SDK_ROOTor the default location and neither variable is set, Stim passes its path to Gradle asANDROID_HOME. fbb30af@stim-cli/server:- The iPhone Duo streams as H.264 through the frames helper, following its lit panel, with the posture in each video packet. 36773c9
Performance
statusandstatus --watchread each state file once per computation, reuse a worktree's git summary until its git files change, reuse the simulator poller's listing (polled every 5 seconds), and refresh only the log facts on a log append. cbef9ab, ccf1ccb, aec1017, 020d18dgclists devices once and asks GitHub once per repository. beec18a@stim-cli/serverserializes each status push once for every phone, and keeps a frame source alive briefly after its last listener leaves. c4a43f4, ebb874f
Fixes
gcdeletes only devices this Stim home created. Astim-name or an AVDconfig.inino longer proves ownership, because everySTIM_HOMEwrites the same ones;gclists another home's devices with the command that deletes them. 3e04bf9- A build is never stored under a cache key its inputs no longer match. Both platforms fingerprint again after the compile; when the app config, a config plugin or another source changed while the build ran, Stim installs the build but stores nothing locally, in the cache provider or remotely. 4e51a7d
ios --runtimerefuses when the slot's simulator runs another iOS version, instead of booting it. 48f1195- A launch whose app process is gone when the bundle wait ends fails as FATAL, "the app process exited", with the device log's errors, instead of reporting
bundlingorunverified. 66ac203 stim androidrefuses withSTIM_BUILD_FAILEDbefore Gradle runs when there is no SDK at the resolved path and noandroid/local.properties. fbb30af- Emulator teardown verifies that every emulator process for the AVD exits. A hung emulator gets SIGTERM, then SIGKILL, once its identity is verified; one that survives fails the teardown and keeps its record. 6c840a5
- A bare project resolves its packages through its own
node_moduleschain, notNODE_PATH, so a pnpm bin shim no longer resolves another workspace member'sexpo. 40d49a4 logs --errorskeeps an Expo error's stack past interleaved bundle response records, and attaches its code frame in--jsonand--follow. 91c3cb6, 86bdfa5- iOS "Synchronous URL loading" diagnostics are recorded as warnings, so a launch does not count them as app errors. 9726693
- The iOS boot memory-pressure warning repeats only when the level changes. 519b2f6
statusdetects the framework of an app registered only through a named port orstim web. 4b5c2a4- Stim starts Stim Desktop without the command's
STIM_HOME, so it reads the same home as when you open it yourself. 9158517 - On Windows, two log files no longer share a log error index key. 10fb09d
@stim-cli/core: a failed lock marker write, such asENOSPC, no longer leaves an empty lock directory that blocks later commands, and the lock timeout refusal prints the command that removes an unowned directory. fbcc163@stim-cli/server: astimcommand or control tool call ends at its timeout even while a grandchild process holds its output. 254366e, 1784586
Docs
- A monorepo recipe for a Vite web package with a base path and HTTPS. a73170d
Migration notes
stim webdoes not start Metro for Expo web. Runstim start, thenstim web. Thestim web --jsonpayload has nodevServerfield.lastActivityAtinstatusis rounded down to the minute.status --watchapplies a log append no sooner than 15 seconds after the previous refresh, so a log-driven update, such as the error count or device activity, can lag by up to 15 seconds.gcno longer deletes a device only because its name or AVDconfig.inimatches Stim's format. A device created before the ledger that this home's config does not record in the device pool or as owned by a project is listed underunverifiedDeviceswith its delete command; run that command yourself to remove it.ios --runtimerefuses when the workspace's simulator runs another iOS version. Remove the simulator withstim worktree removeorstim gc --delete, or pass--slot <name>to create one beside it.gc --worktreesand the idle sweep also remove clean worktrees whose pull request was merged or closed. A closed pull request whose remote branch is gone keeps a worktree with local commits.- A launch whose app exited before the bundle wait ended now fails instead of returning
launched: "unverified"or"bundling". - A build whose inputs changed while it ran is installed but not cached, so the next run with the same inputs compiles again.
gc --deletetrims Metro, client and device logs over 16 MiB to their newest 8 MiB.- iOS "Synchronous URL loading" diagnostics no longer appear in
logs --errors. stim androidrefuses before Gradle when no Android SDK is found. SetANDROID_HOMEor writesdk.dirintoandroid/local.properties.- In a monorepo,
ports,settings,logs,reload,stopandstatusrun from a package that depends on neitherreact-nativenorexpoact on the one Stim app registered in the same Git worktree.ports stoporstopthere stops the app's ports or workspace. A package that already holds ports keeps them until you release them. - Stim starts Stim Desktop without the command's
STIM_HOME, so a simulator booted under anotherSTIM_HOMEdoes not appear in it. Pass--simulator-app xcodeto see it.
QA
No-QA stable release explicitly authorized by the maintainer on 2026-09-26.
Omitted locally before the version commit: format:check, lint, typecheck, knip,
unit tests, test:e2e, test:runtime, test:compat, and the pre-tag native/manual
matrix. Retained before the version commit: candidate build, CLI and server version,
CLI help, guide agent, web --help, and tarball inspection of all six packages. Retained after push:
exact-release-commit CI.
Each included change merged with passing CI. Pull requests record their own real-tool checks.
1.12.0
New
iosSimulatorAppandandroidEmulatorAppdefault tostim-desktopon macOS while Stim Desktop is installed, found by its bundle id through Launch Services. Without it the defaults stayxcodeandemulator, and a set value always wins.stim settingsshows such a default as(default: Stim Desktop installed)and--jsonaddsdefaultReason. 5ebf6aeios --planandandroid --planreport why they predict a cache miss, asmissReasonin the plan payload. ae99e71- AVDs Stim creates get a hardware keyboard (
hw.keyboard=yes) unlessandroid.avdConfigorandroid.avdConfigFilesets it. A parked AVD from before this default gets the key when it is adopted. f0f550f stim status --jsonreports structuredissues(code, severity, message, remedy) besidewarnings, and an owned emulator'sdeviceProfile. 52bfcb7, 8e072a3- The machine setting
gc.worktreeGraceMinutes(default 120, envSTIM_GC_WORKTREE_GRACE_MINUTES) keeps a merged or idle worktree fromgcuntil that long after its last activity. A booted owned device or an unexpired device lease also keeps a worktree. 4a23ae1 @stim-cli/server:- Paired devices with
controlcan drive a Stim-owned simulator or emulator with touch, text and hardware buttons in a control session held under astim device locklease. c1e6cb4 input.rotateandinput.posturerotate a device, fold an iPhone Duo and move a foldable emulator's hinge. d64d5eebuild.planruns one plan at a time per workspace. 80ca4de
- Paired devices with
Fixes
- App id detection evaluates a dynamic
app.config.*throughexpo config, so a non-literalbundleIdentifierorpackageis found before prebuild. b22a317 android --device-profilerefuses a profile such aspixel_foldon a system image that cannot boot it, before building or creating an AVD. 5dd08fdstim stopcloses only agent-device sessions that belong to this workspace's stopped device. 1b1ffb3stim statusno longer warns about an idle workspace's shut-down emulator or a supervisor record proven stale.stop --slotforgets that slot's launch record. 52bfcb7- A remote EAS start waits for another workspace's session start to finish, with a notice, instead of failing with
STIM_LOCK_TIMEOUTafter 4 minutes. 3f26806 @stim-cli/server:- A
logs.queryorlogs.subscribewhoseslotorgrepcannot be astim logsargument getsbad-requestinstead of closing the connection or stopping the server. 85ff2f8 - Simulator input from the frames helper keeps working on Xcode 27 after a CoreDevice client such as Device Hub has touched the simulator. 370fa2f
- A
Docs
- Quitting Device Hub on Xcode 27 shuts down every booted simulator on the machine, not only the ones it opened. ff21e53
Migration notes
- With Stim Desktop installed and neither viewer setting set, owned simulators open in Stim Desktop and owned emulators boot without a window. To keep the device windows, run
stim settings set iosSimulatorApp xcodeandstim settings set androidEmulatorApp emulator.
QA
No-QA stable release explicitly authorized by the maintainer on 2026-09-26.
Omitted locally before the version commit: format:check, lint, typecheck, knip,
unit tests, test:e2e, test:runtime, test:compat, and the pre-tag native/manual
matrix. Retained before the version commit: candidate build, CLI and server
version, guide agent, and tarball inspection of all six packages. Retained
after push: exact-release-commit CI.
Each included change merged with passing CI. Pull requests record their own
real-tool checks. #1394 was checked against the real Launch Services lookup
and a running Stim Desktop with a scratch STIM_HOME.
1.11.0
New
stim status --jsonreports each worktree's git state: changed and untracked files, ahead and behind its upstream, and whether the branch is merged. The merged check is cached per worktree, and paths macOS protects are skipped so status never triggers a privacy prompt. #1230- A cache miss says why.
iosandandroidprint one line naming the changed native inputs, for examplecache miss: native dependency added: expo-clipboard, andstatus --jsonreportslastBuilds.<platform>.missReason. #1228 stim status --jsonreports each platform's last build, andios --plan/android --planpredict whether the next build hits the cache. #1178android --device-profile <id>and theandroid.deviceProfilesetting choose the owned AVD's hardware profile (defaultpixel_6), such aspixel_foldorpixel_tablet. Parked emulators are only reused for the same profile. #1268- The machine setting
androidEmulatorApp(emulatororstim-desktop) boots new owned emulators without a window on macOS and shows them in Stim Desktop. #1292 stim stop --slot defaultstops only the workspace's default device. #1176stim device lockcan lock the workspace's own simulator or emulator. #1261stim logs --source agentlists agent-device actions (taps, typing, screenshots) as log records, attributed to the device they drove. #1179stim-server:- Paired devices granted the
controlscope (stim-server pair --controlorstim-server devices grant <id> --control) can runreloadandstop. Every action is audited, andstim-server logprints the audit. #1187 machine.getreports the Mac's memory used and CPU usage, andmachine.historyreturns the last hour of samples. #1206, #1253, #1267build.planpredicts the next build. #1189- Device frames come from a native helper, compiled on first use, instead of repeated
simctlscreenshots. Clients that decode H.264 receive hardware-encoded video. #1254, #1265 - Frames report the lit iPhone Duo panel and the posture of iPhone Duo and Android foldables. #1234, #1277
- The status payload also exposes the home folder and the per-volume machine usage used by the phone app. #1182
- Paired devices granted the
Fixes
stim stopinterrupts aniosorandroidbuild it would leave nothing to deploy to, instead of waiting silently for it to finish. A billable remote session is ended without waiting on the build, and any command that waits on the workspace says what it is waiting for. #1300ios --remote easandandroid --remote eascreate the remote session after the local build is ready, so a long build no longer leaves the EAS host's daemon idle until it exits. #1216stim ioswithiosSimulatorAppset tostim-desktopopens Stim Desktop in the background instead of bringing it to the front. #1256
QA
No-QA stable release explicitly authorized by the maintainer on 2026-09-25.
Omitted locally before the version commit: format:check, lint, typecheck, knip,
unit tests, test:e2e, test:runtime, test:compat, and the pre-tag native/manual
matrix. Retained before the version commit: candidate build, CLI and server
version, guide agent, and tarball inspection of all six packages. Retained
after push: exact-release-commit CI.
Each included change merged with passing CI. Pull requests record their own
real-tool checks: throwaway simulators and emulators, scratch STIM_HOME runs,
and a real stim stop interrupting a local iOS compile for #1300. The
@stim-cli/server tarball now carries the Swift sources of its frame helper,
which the server compiles on first use.
1.10.1
Fixes
stim-serverpairs through thetailscale serveroute on port 7443 by default and refuses to pair while any serve route is exposed through Tailscale Funnel. #1168stim-serverreports a slow or timed-out device frame capture as delayed instead of failed. #1170- The default iOS simulator is a numbered iPhone on the newest runtime that has one, instead of a special model such as iPhone Duo;
ios.deviceTypeand--device-typeare unaffected. #1174
QA
No-QA patch release explicitly authorized by the maintainer on 2026-09-25.
Omitted locally before the version commit: format:check, lint, typecheck, knip,
unit tests, test:e2e, test:runtime, test:compat, and the pre-tag native/manual
matrix. Retained before the version commit: candidate build, CLI and server
version, guide agent, and tarball inspection of all six packages. Retained
after push: exact-release-commit CI.
Each included change merged with passing CI. #1174 was checked against the
real simctl list runtimes output on a Mac whose newest runtime offers only
iPhone Duo.
1.10.0
New
stim iosandstim androidstart the workspace's dev server when it is not running (including after an idle stop), through the same path asstim start; they no longer refuse withSTIM_NO_METRO. Release configurations,*Releasevariants, and--no-metro-checkstill skip Metro. JSON results reportdevServer: { started, reason }when a run started it. #1163- Disk and memory budgets: before
ios,android, andstartbuild or boot anything, Stim checks free disk on the workspace and$STIM_HOMEvolumes and committed memory. Belowbudget.minFreeDiskGb(default 20) it reclaims in a safe order (idle devices, idle dev servers, build outputs of idle workspaces, cache entries unused for 14 days) and reports each step; belowbudget.hardFloorDiskGb(default 5) it refuses withSTIM_LOW_DISK.doctorreports the budget. #1132 - Idle dev servers stop themselves after
metro.idleStopMinutes(default 60,0disables) with no bundle request, client log, or Stim command, never during a build, a driven device, a device lock, or an open bundle request;statusshowsstopped (idle). #1138, #1144 gcreports every Stim-managed worktree with a reason, andgc --deleteremoves worktrees whose branch was merged (merge, rebase, or squash) with no uncommitted or unpushed work. With--older-than N,gc --deletenow also removes only parked devices parked at least N days ago instead of emptying the pool. #1133, #1161- Device activity:
stim statusreports each device as driven (by agent-device, astim device lock, or a driver process), active, idle, or unknown;gclists idle devices andgc --idle <duration>shuts them down. #1101 @stim-cli/server(stim-server), a new read-only server for remote clients: QR pairing with single-use tokens and revocable device tokens bound to a Tailscale node, and live status, logs, stats, settings, and device screenshots over a WebSocket. It powers the Stim mobile app (not yet published). #1139, #1145, #1150@stim-cli/coreexposes astateentry point with the read side of Stim's state (paths, config and workspace state, settings registry, ledgers, logs, payload types). #1131- Parked simulators are erased and parked emulators have their user data wiped, shrinking each parked device from about 2 GB to a few MB; adoption now cold-boots and reinstalls. #1140
worktree warmwarns when the carriednode_moduleswere installed from a different lockfile (npm, pnpm, Yarn Classic, Yarn Berry), with the install command to run. #1126guide agenttells agents to leave the machine clean, and the website documents a Claude CodeSessionEndhook that runsstim stop. #1128
Fixes
- Regenerate a stale, gitignored
ios/orandroid/before a cache-miss Expo build, so an app built from an old native dir is never stored under the new fingerprint; read the iOS scheme and Android package after regeneration. #1142, #1148 gcand every boot, shutdown, park, and delete trust only devices Stim created (a new creation ledger, or the exact legacy naming format), never anystim-*name. #1105- Remote iOS installs on EAS Simulator sessions work again (agent-device refused them under the proxy lease); a failure after a billable session started names the session and
stim stop. #1127 - Refuse early when eas-cli is too old: below 21.6.0 for
--remote eas, below 18.9.0 for--eas-profile. #1095, #1129 - Remote Android launches report the public Metro origin and skip adb-only steps. #1130
stopandgc --idleverify a simulator actually shut down and report failures with a remedy; parking waits for the shutdown to settle before erasing. #1158, #1156statuslists worktrees with no environment for every registered repository, not only the current one, and no longer lists a worktree whose inner package has an environment. #1106settings --jsonreports environment-provided values with their registry type. #1152- The release smoke test waits for npm to serve new versions before failing. #1109
QA
No-QA stable release explicitly authorized by the maintainer on 2026-09-25,
although this release changes package boundaries (a sixth package,
@stim-cli/server) and the Release workflow, which RELEASE.md assigns to the
full lane. Omitted locally before the version commit: format:check, lint,
typecheck, knip, unit tests, test:e2e, test:runtime, test:compat, and the
pre-tag native/manual matrix. Retained before the version commit: candidate
build, CLI version/help/guide, and tarball inspection of all six packages.
Retained after push: exact-release-commit CI, including its format, lint, build,
typecheck, knip, unit, cross-platform e2e, and published-runtime jobs.
Most included changes were reviewed by a fresh agent (and several by Codex)
and merged with passing CI; some were merged without a fresh review at the
maintainer's request. Pull requests record their own real-tool checks
(throwaway simulators and emulators, scratch STIM_HOME runs, real EAS
Simulator sessions for #1127 and #1130). The Release workflow publishes
@stim-cli/server for the first time; its name was reserved with a 0.0.0
placeholder and a trusted publisher.
1.9.0
New
stim status --watchstreams status: with--jsonit prints one complete payload per line, immediately and then on every change, and never repeats an identical payload. It reacts to Stim state changes,adb track-devices, and simulator state, and exits cleanly on SIGINT, SIGTERM, or a closed stdout. #1080stim statusreports the build in progress for each workspace: platform, slot, phase (prepare,cache-lookup,wait,prebuild,pods,compile,install,launch), elapsed time, and an estimate from the median of comparable previous runs. A build whose process is gone shows asstale. #1078stim settingsreads and writes settings by scope:stim settings --jsonlists every setting's effective value, origin layer, and per-layer values;get,set, andunsettake--scope machine|workspace|repo|committed. One registry now defines every setting, and the package ships a JSON Schema (dist/settings.schema.json) that.stim.jsoncan reference with"$schema". #1079stim statuslists each workspace's remote EAS Simulator sessions with their preview URL; plainstatusmarks them billable. #1077stim gc --jsonprints the report as JSON, andgcreclaims workspace build outputs, orphaned workspace directories, and finished worktrees. #1043, #1021iosSimulatorApp: "stim-desktop"andstim ios --simulator-app stim-desktopopen owned simulators in Stim Desktop instead of Device Hub. #1057stim ios --deviceinstalls on Wi-Fi-paired iPhones. #1020- Owned Android emulators boot with their gRPC endpoint enabled on
127.0.0.1(console port + 3000) with token authentication, so tools can stream frames from headless emulators. #1064
Performance
stim statusis about 20 times faster (about 3.3 s to 0.18 s on a machine with 21 workspaces): error counts come from a per-workspace index that reads only newly appended log bytes, and device, git, and port lookups run once per call and in parallel. The first run after upgrading builds the index. #1081- Workspace NDJSON logs are capped, and the timeline is read once per query. #995
Fixes
- Decode iOS provisioning profiles without touching the keychain. #1036
- Use the agent-device proxy lease for remote devices, and name remote sessions after the workspace. #1029, #1037
- Keep the Metro port reserved while its supervisor is live, keep other projects' registry entries when reusing a port, and stop or keep the port of an Expo child orphaned by its supervisor. #984, #983, #1000
- Keep build cache entries intact during concurrent publishes and swaps, keep build claims held while an orphaned build tool runs, and report a remote cache upload with no destination as failed. #991, #990, #985
gcnever trims a declared cache that contains a protected root, reports cache entries it cannot remove, and exits 1 when--deletecannot delete or empty an entry. #989, #1009, #994- Refuse git-blocked worktree removal before reclaiming devices. #987
- Bound device tool calls that could hang every workspace, and bound the xcodebuild and pod install transcripts in memory. #986, #1004
- Key Expo Android debug builds on the device ABI, and claim the console port for the Android boot fallback. #992, #1002
logs --followstarts after startup crash capture. #998- Refuse outside a project with
STIM_NO_PROJECTfrom every command; refuse relativeSTIM_HOMEand cache path overrides, aconfig.jsonthat is not a JSON object, and unknown remote, tunnel, AndroidcompilerCache, andpchchoices. #1001, #1005, #999, #1012, #1006 - Keep
cache.optionsfrom reaching a provider that another settings layer names, and remove the committed.stim.jsoncachessetting. #997, #1017 - Record doctor runs only for app projects, and print escaped errors without a Node stack trace. #993, #988
- Settle Windows access denials for vanishing claims, and close the Windows follow-ups from the #871 review. #1034, #1027
Docs
- The static skill points agents at
npx stimwhenstimis not on PATH; READMEs are trimmed to landing pages; website gaps are filled and the first troubleshooting codes grouped. #1042, #1013, #1007 - "Worktrees with no environment" in
statusis documented as linked worktrees Stim has not registered, whichworktree warmdoes not change. #1065
Stim Desktop
The repository now contains Stim Desktop (apps/desktop), a macOS app for supervising Stim workspaces: live iOS and Android frames with take-over input, projects, logs, build progress, resource usage, settings, and cleanup actions through the CLI. It is not part of the npm packages and is not distributed yet; build it with apps/desktop/scripts/bundle.sh. #1047
QA
No-QA stable release explicitly authorized by the maintainer on 2026-09-25.
Omitted locally before the version commit: format:check, lint, typecheck, knip,
unit tests, test:e2e, test:runtime, test:compat, and the pre-tag native/manual
matrix, which scripts/release-qa-matrix.mjs v1.8.0 marks fully required (8 of
8 rows). Retained before the version commit: candidate build, CLI
version/help/guide, and tarball inspection. Retained after push:
exact-release-commit CI, including its format, lint, build, typecheck, knip,
unit, cross-platform e2e, and published-runtime jobs.
Unlike earlier no-QA releases, most changes from #1047 onward were merged
without a review and before their CI finished, at the maintainer's request.
Main CI was restored to green in #1086 and #1084 before this release.
Individual pull requests record their own real-tool checks (throwaway
simulators and emulators, a scratch Expo app build for build progress, a
scratch STIM_HOME); the native E2E suites were not run against this version.
A real EAS Simulator session was not exercised: #1090 tracks an early refusal
for eas-cli versions without simulator commands.
1.8.0
New
- Choose Siniulator or Apple's viewer with the machine-wide
iosSimulatorApppreference. Override it for one local launch withstim ios --simulator-app siniulatoror--simulator-app xcode, including an already running owned simulator. The override does not change the saved preference. 072ddc7c5, 5600b9e26
Fixes
- Open owned simulators with the selected Xcode's Device Hub on Xcode 27, with Simulator retained for older Xcode versions. Window opening remains best effort. 642606173
- Verify Cloudflare Quick Tunnels and remote Metro reachability when the system DNS resolver still has a stale answer for a new tunnel hostname. 9c9ac0d8e
Docs
- Restore the documentation site's shared theme context so its theme switch and production build work together. 2951dbcf3
QA
No-QA stable release explicitly authorized on 2026-09-21. This release contains the same product changes as 1.8.0-rc.1. Omitted locally before the version commit: format:check, lint, typecheck, knip, unit tests, test:e2e, test:runtime, test:compat, and the pre-tag native/manual matrix. Retained before the version commit: candidate build, CLI version/help/guide, and tarball inspection. Retained after push: exact-release-commit CI, including its format, lint, build, typecheck, knip, unit, cross-platform e2e, and published-runtime jobs.
Included changes were reviewed and merged with passing CI in #942, #945, #947, #949, and #952. Their existing validation includes a live Quick Tunnel HTTP/WebSocket check, the docs production build, and Siniulator displaying the existing iPhone Duo with the per-launch override. These observations were made before the version bump; the native/manual matrix was not rerun for this stable release.
The published tarball README is verified. Empty npm registry README metadata is tracked separately in #953.
1.8.0-rc.1
New
- Choose Siniulator or Apple's viewer with the machine-wide
iosSimulatorApppreference. Override it for one local launch withstim ios --simulator-app siniulatoror--simulator-app xcode, including an already running owned simulator. The override does not change the saved preference. 072ddc7c5, 5600b9e26
Fixes
- Open owned simulators with the selected Xcode's Device Hub on Xcode 27, with Simulator retained for older Xcode versions. Window opening remains best effort. 642606173
- Verify Cloudflare Quick Tunnels and remote Metro reachability when the system DNS resolver still has a stale answer for a new tunnel hostname. 9c9ac0d8e
Docs
- Restore the documentation site's shared theme context so its theme switch and production build work together. 2951dbcf3
QA
Expedited RC authorized on 2026-09-21. Omitted locally before the version commit: format:check, lint, typecheck, knip, unit tests, test:e2e, test:runtime, test:compat, and the pre-tag native/manual matrix. Retained before the version commit: candidate build, CLI version/help/guide, and tarball inspection. Retained after push: exact-release-commit CI, including its format, lint, build, typecheck, knip, unit, cross-platform e2e, and published-runtime jobs.
Included changes were reviewed and merged with passing CI in #942, #945, #947, #949, and #952. Their existing validation includes a live Quick Tunnel HTTP/WebSocket check, the docs production build, and Siniulator displaying the existing iPhone Duo with the per-launch override. These observations were made before the version bump; the native/manual matrix was not rerun for this candidate.
1.7.0
New
- The Android development loop runs on Windows, including owned emulators, Metro supervision, native builds, installation, logs, worktrees, and caches. 183f05b8e, f5a6df25d, f700814d9, 156117682
stim ios --remote eas --eas-profile <profile>works from a Windows host using a completed EAS development build and a remote simulator. f1753ee8c, 9a8aaff2f
Removed (breaking)
- Node 20 support ended. Running Stim requires Node 22.12.0 or later. 87285918d
Fixes
- Windows Android runs retain the selected emulator ABI across repeated slots and wait for the emulator's package manager before installing. 3b5aea298
- Windows claim acquisition yields between transient publish retries, avoiding a spurious refusal when other processes repeatedly remove and recreate the claim directory. 8e8ac8e10
- Windows worktree removal handles long paths, and claim acquisition treats an access denial for a disappearing claim as a retryable absence. 7855bbe46, a5ff2b896
- Windows release APK swaps and named Metro ports work without Unix
cp,mv,zip, orlsofcommands. 23c13116e - Windows Android builds report insufficient native path room before Gradle runs. Supervisor startup no longer inherits a pipe that keeps the parent waiting, and emulator cleanup handles the crash-consent process. 156117682
- Bare React Native launch remedies no longer suggest opening Expo's development-server picker. 589bfe032
- Android emulator debug launches route Metro through the existing ADB reverse connection, so a cached install can load its bundle on a newly started emulator. 095797d7f
- iOS simulator launches recover when CoreSimulator reports no process handle even though the app started, after confirming the app was absent before launch and running afterward. 18c7cae7f
- Clean worktree removal recognizes
@-prefixed branch names when checking for unique commits. 2800d7279
Docs
- Restored the root Stim README after it was replaced by the video-project README. 288547ef8
- Documented Windows host requirements, Android path limits, and the EAS Simulator route for iOS. 631986f02
- Published refusal-code remedies on the troubleshooting page. dab1187c5
Migration notes
Upgrade to Node 22.12.0 or later before installing this release. Repository development requires Node 22.18.0 or later. On Windows, use a short project path or a subst drive for Android native builds; the requirements page lists the host tools and the remote iOS path.
1.6.0
New
- Swift joins the shared Xcode compilation cache automatically. When
xcrun swiftreports Swift 6.4 or newer (Xcode 27) and the project'sreact-nativeis 0.87 or newer,stim iospassesSWIFT_ENABLE_COMPILE_CACHE=YESwith Swift prefix mappings for the workspace root and DerivedData, so Swift output crosses worktrees like clang output already did. Older toolchains, where prefix-mapped batches crashed swift-frontend, and React Native before 0.87, which disables the explicit modules Swift caching requires, stay off. The pre-build cache note names the outcome (Swift on,Swift on, unmapped,Swift off, 6.3 < 6.4,Swift off, react-native 0.86 < 0.87), and the post-build line counts any Swift targets Xcode still excluded for building without explicit modules.optimizations.ios.swiftCompilationCacheis unset by default;trueforces the cache on any toolchain, unmapped below Swift 6.4, andfalsekeeps it off. 2ece2d34d
Migration notes
Preserve existing Stim state when upgrading. The artifact cache key is unchanged for an unset or false swiftCompilationCache; an explicit true keeps its own key as before. On a Swift 6.4 toolchain with React Native 0.87 or newer, the first stim ios after upgrading recompiles Swift targets once to populate the shared cache.
QA
Native compatibility, simulator/device suites, and manual QA are omitted at the user's request on 2026-09-19. The omission is not a native-compatibility pass. The Swift caching path was verified by hand on 2026-09-18 with Xcode 27.0 on an Expo SDK 58 app across two worktrees, but not by the native e2e suites, whose iOS lane has not yet run on a Swift 6.4 runner.
Retained checks: formatting, lint, candidate build, typecheck, knip, unit tests, end-to-end tests, published-runtime checks, candidate CLI version/help/guide, inspection of all five packed tarballs, and CI on the exact release commit before tagging.
1.5.0
New
stim guideandstim guide agentopen with a STATUS block when doctor is due for a platform in this project, meaning it never ran here, ran more than seven days ago, or ran under another Stim version, and when a newer Stim release exists. Doctor records each run per platform, and the release check reads the npm registry at most once a day;STIM_NO_UPDATE_CHECKdisables it. 56decdaf3stim start --reset-cacherestarts the owned Metro with Metro's own reset, clearing every store in the app's Metro config including the shared transform store, instead of persisting a cache generation in workspace state. The Expo SDK 54 requirement for a reset is gone. 01f290095
Fixes
- Support bare-repository worktree layouts, where every checkout is a linked worktree beside a bare
.git. Warm, remove, doctor, storage diagnostics, and status resolve the source checkout as the worktree on the bare repository's HEAD branch, and refuse with the exact Git command to run when HEAD is detached, its branch has no worktree, or that worktree was deleted. 6b5ad313c
Docs
- Website benchmark details: scenario picker keyed on the run variant, one full-width comparison card, a terminal clamped to the viewport, and a redesigned timeline toolbar with direct scrubbing. 515203498
Migration notes
Doctor now writes to Stim state on every run, so a project appears in stim status after stim doctor alone. The --reset-cache workspace-state generation field is no longer read; existing state needs no change.
QA
Native compatibility, simulator/device suites, and manual QA are omitted at the user's request on 2026-09-18. The omission is not a native-compatibility pass.
Retained checks: formatting, lint, candidate build, typecheck, knip, unit tests, end-to-end tests, published-runtime checks, candidate CLI version/help/guide, inspection of all five packed tarballs, and CI on the exact release commit before tagging.
1.4.0
New
- Reserve named workspace ports for web and API servers with
stim ports get <label>. List allocations withstim ports, stop listeners withstim ports stop [label], or release reservations without killing withstim ports release [label]. Stop supports--dry-run; worktree removal and GC reclaim named allocations. Metro remains separate. beeae86b8
Fixes
- Accept compatible EAS development builds using the CLI's current
app.idfield or legacyproject.id, while retaining project-identity validation. e1b3c4c25 3b984be4a - Give iOS relaunch remedies the commands needed to restart an app. e80a01c08
- Recover abandoned parked-device operation claims through process identity, while keeping live and unverifiable owners protected. d2037c688
- Track the native process group during Android AVD creation so interrupted creation can recover once its writers are proven gone. Reserve AVDs outside short config transactions. 5255dff26 4b712ee54
- Preserve directory lock ownership and recover core locks through the shared process-identity protocol. The core package owns the shared lock implementation. 8a4920418 fbef197ea
Docs
- Use generic web and API server examples throughout the named-port guides and website. 9759b4f9b
Migration notes
Preserve existing Stim state when upgrading. Use the current CLI for cleanup after allocating named ports; older versions do not manage those allocations.
Named reservations do not hold sockets open. Pass the returned port to the server, use strict-port behavior when supported, and verify the bound port. ports stop may stop a listener outside the workspace, so reserve only services that workspace may stop.
QA
Native compatibility, simulator/device suites, and manual QA are omitted at the user's request on 2026-09-14. This release also includes the unreleased fixes and internal refactors since 1.3.1; the omission is not a native-compatibility pass.
Retained checks: formatting, lint, candidate build, typecheck, knip, unit tests, end-to-end tests, published-runtime checks, candidate CLI version/help/guide, inspection of all five packed tarballs, and CI on the exact release commit before tagging.
1.3.1
Fixes
- Give an Android emulator that remains alive under observed memory pressure one additional boot wait, and retain a bounded diagnostic window after the boot deadline. d7687383a6
- Close matching local agent-device sessions when giving up owned simulators and emulators, with ownership checks and bounded cleanup. 0fdf4d3bec
- Allow current worktree refreshes to overlap copies while retaining exclusive ownership for mutations and revalidating after lock escalation. 31839cb137
- Require an ownership claim before copying warm state and provide actionable storage-permission and blocking-file remedies. 10c93ef708
- Refuse log queries for workspaces without a timeline instead of reporting a misleading empty success, and suggest a matching registered descendant when available. 66d21f150a
- Improve test isolation for parallel runs and external temporary directories, and stop cache-suite Gradle daemons before removing their homes. 5b5d1b5201 6d861b2d1c ac63380353 8d59b2d2c9
Docs
- Explain how to investigate React Native Test App fingerprint churn with narrowly scoped generated-file exclusions and cache parity checks. 8095bafa3f
Migration notes
Preserve existing Stim state when upgrading. No state reset is required.
Log queries now return STIM_NO_PROJECT when the workspace has no timeline. Run the command from the intended workspace after starting or building with Stim. Existing empty timelines still return an empty successful result.
Worktree warming no longer copies without an ownership claim. If it returns STIM_CLAIM_UNAVAILABLE, follow the reported process-identity or storage remedy before retrying.
1.3.0
New
- Run multiple simulators, emulators, and physical devices per workspace with
--slot <name>. Reuse named assignments, inspect per-slot logs, and stop one slot while preserving Metro and sibling devices. There is no fixed slot count; configured caps and host resources still apply. c531b2ad0d - Download compatible EAS development builds with
--eas-profile <name>for local or remote targets. Missing builds produce an explicit EAS build command; Stim does not start a cloud build automatically. 18613c83fe
Fixes
- Report local iOS boot progress and sampled memory pressure, retaining the highest observed pressure, unavailable readings, and long observation gaps with safe agent recovery guidance. Blocking CLI work can delay observations and timeout handling. fe33ce2f83
- Prepare profiled native QA simulators sequentially before multi-device workloads, retaining cold-build and simultaneous-device checks and ownership state on preparation failures. Pool QA stops an unused workspace before the next boot. 7d5e18078a fbad065cd9
- Probe iOS readiness using a simulator-native executable instead of a host macOS binary, while keeping the process-spawn timeout and failure handling. 7d2fca7758
- Resolve the Android SDK emulator during native QA cleanup when it is not on PATH, retaining fatal enumeration errors so leaked devices cannot be hidden. dfac53fa0a
Docs
- Explain device slots on the website with a copyable phone/iPad/hardware testing prompt, command examples, cleanup behavior, and shared-Metro limitations. Feature changes now require corresponding website documentation and example updates. f6e566e54b
- Document EAS development profiles and add an example agent prompt. b9b8570d25 9760e5524b
Migration notes
Existing assignments remain in the default slot. Preserve ownership state when upgrading; do not delete it. Use a slot-aware CLI consistently while named assignments exist, since older versions cannot manage them.
Slots share Metro and compatible build caches. Shared Metro events cannot verify an individual slot's Debug launch, so it may be reported as unverified; inspect the intended device before claiming success. Reload remains platform-wide, and named remote sessions are unsupported. Physical-device signing and lease requirements still apply. The parked-device pool policy is unchanged.
QA
All iOS and Android loop/cache jobs passed in the full native run. Its Expo pool timeout led to the idle-workspace fix; both corrected pool jobs then passed in the focused run, using the unchanged standard runners. Local bare/Expo iOS loop, cache, and pool suites passed, including three simultaneous simulator slots and concurrent build-cache races. The final combined source passed 4,332 unit tests (one skip), 87 end-to-end tests, build, type, lint, runtime, and website checks.
The release owner authorized reusing the completed feature QA rather than repeating it for the version-only release candidate. Packaging inspection, CLI version/help/guide checks, and exact-release-commit CI remain required before tagging. Physical-device installs and authenticated EAS downloads were not exercised in this campaign; their command and lease paths have regression coverage.
1.2.0
New
- Native builds can warm Metro while compilation is still running, reducing the wait before the first development bundle. The optimization is enabled by default and can be configured or disabled through Stim's documented optimization settings. 8a7b70d8c
Fixes
- Android emulator recovery is more reliable. Stim can reclaim orphaned AVD data, recover an existing owned emulator, detect when an AVD restarts on a different serial, revalidate ownership before installation, restore port forwarding, and continue on the verified serial. Freshly created emulators also receive a longer first-bundle verification window. cba5f1fd0 748d0053e 750fc98aa 1704b15da
- New Android emulators use an explicit high-density phone display instead of inheriting an unsuitable default. 28f4a1106
- Stalled iOS simulator processes are detected earlier, and failure diagnostics report observed host memory pressure without attributing it to the app or claiming it caused the failure.
doctorrecommends the optional SimSlim setup, while Stim continues to require an explicit profile before changing simulator services. 62404f782 - Local iOS discovery, boot, installation, Simulator opening, and SimSlim reconciliation now have bounded waits. SimSlim cleanup verifies the owned process identity, handles timeouts and terminal signals, and retains its claim when termination cannot be confirmed. 9f8097583
doctorno longer requiresexpo-dev-client; Expo projects can use Stim without that package when their workflow does not need a development client. ff786f1e6doctor --fixchanges the Claude Code sandbox allowance only when the corresponding diagnostic is present. 772b9b356worktree warmskips Finder.DS_Storemetadata and ignored.ideadirectories. 25889af6e 1ddaf82b8
Docs
- Agent guidance routes common situations to the matching versioned guide topic, and long waits point to the workflow guidance that explains diagnosis and recovery. d9adbf1cf 1b76f527b
- Website release-note rendering fences command output that could otherwise be interpreted as page markup. 615800c52
QA
The release owner authorized reusing the completed 1.2 QA campaign instead of repeating the native and manual matrix on the version-only candidate. This is a disclosed deviation from the stable-release gate in RELEASE.md; the exact candidate still receives the full local preflight, tarball inspection, and exact-release-commit CI before tagging.
The retained campaign exercised all four native loop rows and all four native cache rows across bare React Native and Expo on iOS and Android. All cache rows and both iOS loops passed. Both Android loops completed with clean teardown but recorded partial launch qualification because a warm invocation was unverified; later manual recovery showed the expected UI. Manual Expo iOS and bare Android work covered launch, UI, induced errors, structured logs, recovery, worktree isolation, and exact cleanup. The Expo Android check covered picker recovery, healthy UI, and a successful bundle response. No test device or automation session remains active.
The fixes merged after that frozen candidate have separate evidence:
- The simulator-memory fix passed 4,219 unit tests, 84 end-to-end tests, final-head CI, two reviews, and a fresh owned iOS 26.5 Expo launch with real spawn and launch calls.
- Android cold-start and serial-recovery fixes passed integrated static, runtime, unit, and end-to-end checks. A real API 36 emulator was restarted on a new serial; status detected it without mutating the registry, and rerunning restored forwarding and produced a verified launch. The exact emulator and worktree were removed.
- The final iOS deadline and cancellation tree passed formatting, lint, build, type checking, unused-code checks, 4,248 unit tests with one existing skip, 84 end-to-end tests, runtime verification, two fresh reviews, and required CI. A fresh owned iPhone 17 / iOS 26.5 simulator completed discovery, boot, install, and a verified Expo launch with empty error logs. Controlled subprocess checks covered timeout, SIGINT, SIGTERM, immediate exit, descendant cleanup, and retained claims. The exact simulator, worktree, and test processes were removed. No SimSlim profile was applied and no OOM condition was induced.
Omitted at the release owner's direction: rerunning the pre-tag native and manual matrix after the version and release-note-only changes. Existing unresolved pool, authenticated remote-device, and physical-phone coverage are not claimed by this release.
1.1.0
New
stim worktree warm --refreshbrings the source checkout up to date before copying from it: it fast-forwards that checkout to its upstream and installs when the lockfiles moved, so a warm worktree no longer inherits whatever happened to be sitting in the checkout that seeded it. It refuses a source checkout it cannot move (STIM_MAIN_DIRTY,STIM_MAIN_DETACHED,STIM_MAIN_DIVERGED), never switches a branch under another checkout, and warns when the checkout is not on the default branch, because every worktree seeded from there then carries that branch's dependencies. Opt-in: plainwarmis unchanged. 97ede367f- One ownership-claim primitive now backs every lock held across a long operation, and
build-lock,build-slotsandworkspace-process-lockwere migrated onto it. Liveness comes from a process-identity record rather than a bare PID check, so a recycled PID can no longer make a dead claim look alive; a claim is built privately and renamed into place, so a half-built one is never observable; and a spawned installer is held by its process group, so a package manager's postinstall descendants keep the claim while they are still writing. A state the claim cannot resolve is refused with the path to remove, never silently reaped. a6252b9d7 worktree.defaultBranchin the repository-root.stim.jsonnames the default branch whenorigin/HEADis missing or wrong. 97ede367fdoctorreports the source checkout's fitness as a seed -- uncommitted changes, a detached HEAD, a rebase or merge in progress, divergence from upstream, and a branch that is not the default one -- each naming the refusal it predicts and the git line that clears it. 6d0d16bc0doctorvalidates the machine config: a setting naming a path that no longer exists, a setting missing a companion it requires, a key Stim no longer reads, and a config that is not valid JSON. Previously these surfaced only when a command read them, and the error named the missing file rather than the setting that named it. e9d520468- A native build's compiler-cache statistics now survive the build that failed.
--jsonfailure payloads carry a newccachekey on Android andcompilationCacheon iOS, and every native build prints acacheprogress line, failures included. 5197ff384
Fixes
gc --deletecould delete a build lock that was still held. It decided whether a lock was orphaned from a bare PID check, and that guard also passed for a claim record it could not parse -- so a corrupt record belonging to a running build was treated as garbage, leaving two workspaces free to compile the same app at once. It now surveys claims through the shared primitive, takes the set's own claim before removing anything, and leaves a claim it cannot resolve alone and reported. a6252b9d7- A
STIM_HOMEthe filesystem will not accept writes to no longer stops a plainwarm. macOS reports a recursivemkdiragainst a read-only mount asENOENT, which is also what a claim set another process just removed gives, so the attempts were spent as if contended and the copy refused naming a contender that was never there. Such a store is now identified as missing rather than contended, and the copy degrades the way it did before claims existed. 68d9e2fe5 warmoutput names what it is talking about. The line reporting a wait attributed the waiter's own elapsed time to the holder's pid, and the line reporting an install named neither the tree it installed in nor the one it copied from. aa09f4015- A transient
adb shell pm clearfailure when adopting a pooled Android emulator is retried rather than failing the launch. The first launch on an emulator resumed from a snapshot could fail while its system services were still restarting, where the identical command succeeded seconds later. 3e2552ac5 - A cleanup whose staging removal fails now releases the claim it took instead of keeping it. 95d2b7e3f
stim reloadno longer kills the app it was asked to reload. It reopened the recorded development-client URL, which restarts rather than reloads; on Android that recreatesMainActivity, and expo-dev-launcher asserts there that no React context exists yet, so a healthy dev-client app died on everystim reload androidwhile the command reported success because it dispatched without observing the result. Reload now goes through the Metro websocket on both platforms. 35c6bab80- A hand-edited
config.jsonno longer crashes a command with a rawTypeError. A missingprojectscontainer is adopted, the way a missingreposalready was, and a container holding an array or a scalar is reported asSTIM_CONFIG_CORRUPTwith the file left untouched. That second shape was the quiet one:JSON.stringifydrops a string key set on an array, so"projects": []let a write return the record it appeared to have stored while nothing reached disk. 2efb9db84 - Owned devices are now distinguished across worktrees. The default label combines the worktree directory and the app directory (
pr6460-tlon-mobile), and an iOS name gains a suffix when two workspaces would otherwise collide, serialized under its own lock. Two worktrees of one repository used to compete for a single device named after the app directory alone. An existing owned iOS simulator is renamed when it is next reused. acc1d7c29 - A CAS toolchain that is missing, unreadable, or unusable no longer refuses the Android build. Stim warns, names the setting and the file it came from, and falls back to ccache where ccache is available. A stale
optimizations.android.casToolchainpointing at a deleted directory used to fail everystim androidinstantly withSTIM_BAD_ARG, whiledoctor --platform androidreported clean. e9d520468 - A non-string
optimizations.android.casToolchainno longer makesgcrefuse. e9d520468 doctorno longer reports the source checkout's branch state to people who do not use worktrees.The source checkout is N commits behindfired unconditionally, so working in a single checkout on a feature branch read as a problem rather than as the job. Those findings are now gated on the repository actually having a linked worktree. Findings about the source checkout's dependencies, Pods and cold build output are not gated and still appear in a single checkout. 6d0d16bc0- Command start-up loads only the command being run. 9a23aebd7
- A long run of benchmark-harness fixes: sessions no longer fail on a refusal that changed nothing, and runs pin their locale so native cache entries are reused.
Docs
guide agentandguide lifecyclename the two ways of working -- a single checkout, and the worktree workflow where the source checkout is a seed that stays clean and current -- and say which rules belong to which. Previously the worktree workflow read as the only one, and its rules about the seed looked universal. 6d0d16bc0- Shipped text now calls the repository's primary working tree the source checkout rather than the main checkout. The old name read as the checkout of the
mainbranch, and plenty of repositories have none -- a project whose default branch isdevelopgotThe main checkout is 3 commits behind origin/develop. No command, flag, settings key, or error code changed. 99de2b229
Migration notes
- Let a running build finish before upgrading. The build lock changed shape, so a 1.0.0 build already in flight and a 1.1.0 build started after the upgrade do not see each other's record and will both compile. Running two Stim versions against one
STIM_HOMEis not a supported configuration; this is only about the moment of the upgrade, and it clears as soon as the older processes exit. - Output that said "main checkout" now says "source checkout". Nothing executable changed:
worktree.defaultBranch,worktree.excludeand every other settings key keep their names, andSTIM_MAIN_DIRTY/STIM_MAIN_DETACHED/STIM_MAIN_DIVERGEDkeep theirs. A script matching on the old phrase in human output needs updating. So does one matching ondoctor --json, whose finding titles, details and remedies are the same renamed strings; only the field names are untouched. stim worktree warmis unchanged without--refresh, with one exception: it now refuses to copy after an install that did not finish, because copying a half-installednode_modulesinto a worktree produces a broken worktree silently.stim worktree warm --refreshreinstalls and clears it.--refreshneeds a clean source checkout on a branch that can fast-forward. In a worktree workflow that is what keeps the checkout worth copying; if you also work in it directly, omit the flag. It is best-effort about the network: a fetch that fails is reported and the refresh continues against what is already local, and a branch with no upstream is left where it is.- A machine whose
optimizations.android.casToolchainhas rotted keeps building. Stim warns, names the setting and its file, and uses ccache where available;doctorreports the same thing so it is findable before a build rather than during one. CAS itself is unchanged, and a valid toolchain is still used.
Known limitations
Recorded in #696 rather than fixed, and documented in guide errors and guide lifecycle where a user would meet them:
- A plain
warmthat cannot record a claim surveys the claim set and refuses if a refresh holds it, which closes the common case but not a refresh that starts after the copy is already running. Reaching it needs one process unable to writeSTIM_HOMEwhile another can. That survey also only treats an unreadable.claimfile as a blocker, so a refresh whoseexclusive/directory itself cannot be read is not seen. - A process-group id carries no identity, so a recycled one reads as alive. It holds a claim too long; it never frees a live writer.
- An installer descendant that never exits makes
--refreshwait, then refuse after five minutes naming the process group and the command to end it, keeping the claim. STIM_HOMEor itswarm-locksdirectory existing as a plain file refuses with a remedy that names a path one level too deep.
QA
Cut with the expedited preflight plus the full caches matrix, at the release owner's direction. RELEASE.md section 3 excludes a stable version from the expedited lane and asks for every applicable row on a minor release, so this is a disclosed deviation from the documented gate rather than a completion of it. What it does not omit any more is the cache evidence.
Why the cache rows specifically: this release migrates the single-flight build lock, the build-slot semaphore and the workspace process lock onto one primitive, and RELEASE.md names the caches row as the required evidence for "build, cache, fingerprint, Pods, Metro, single-flight, or gc behaviour". A lock change without that row is the one thing this release could not claim.
Run on the version commit:
pnpm install --frozen-lockfile,release:prep --check(5 packages at 1.1.0, 7workspace:ranges),build,--version,--help,guide agent, and the tarball inspection.format:check,lint,typecheck,knip,test,test:e2e.node test/e2e/native/run-cache-e2e.mjs --framework expo --platform ios-- 7 pass, 1 skip, 0 fail, 0 missing in 327s. The skip isgradle-cache, which is Android-only.node test/e2e/native/run-cache-e2e.mjs --framework expo --platform android-- 6 pass, 2 skip, 0 fail, 0 missing in 363s. The skips arexcode-casandpods-reuse, both iOS-only.
Every check passes on the platform where it applies, on real compiles in throwaway workspaces.
Both rows were run on 0bbdead9c, the candidate before the final five commits -- aa09f4015, 3e2552ac5, 68d9e2fe5, 95d2b7e3f and e22636dd0, which are the four fixes listed above plus a docs change. They were not re-run against the tagged tree, at the release owner's direction. Three of the four touch code the suite exercises: the claim store's error classification, warm output wording, and Android pool adoption. What covers them instead is the unit and e2e suites, which did run on the tagged tree and include the tests those commits shipped with, plus exact-release-commit CI.
The evidence that bears directly on the lock migration is single-flight, on both platforms, with two workspaces racing one uncached fingerprint.
iOS, through xcodebuild:
wt3: cacheHit="local" waitedForBuild={"pid":37959,"ms":67452} durationMs=85630
wt4: cacheHit=false waitedForBuild=null durationMs=85659
both racers computed the same key: b75c50589e37f1b49ff92feca64e128fc05852b2-debug-sim
Android, through Gradle:
wt3: cacheHit="local" waitedForBuild={"pid":67582,"ms":23053} durationMs=67306
wt4: cacheHit=false waitedForBuild=null durationMs=63857
build waited 23.1s for .../e2e-cache-4's build -> installed from cache
One workspace compiled and the other waited on the claim and installed what it stored, on each platform. The surrounding checks cover the rest of what the migration touches: gradle-cache proves --build-cache reached the real argv with 651 new entries and 89 FROM-CACHE tasks in the second worktree; xcode-cas proves all five compilation-cache settings reached the real argv with 69% of the cold compile's CAS files reused by a never-compiled workspace; pods-reuse proves a carried, matching Pods skips pod install outright rather than merely running it quickly; and zero-config proves the source checkout was porcelain-clean afterwards with all four worktrees removed without --force.
gc-view reports every cache with a size and calls none of them garbage on both platforms. It does not reach gc --delete, so the repaired deletion guard -- the fix in this release that previously decided deletions from a bare PID check -- has unit coverage only.
Omitted: the pre-tag native loop rows and the manual field-test matrix in RELEASE.md section 3.
Retained after push: exact-release-commit CI, including its format, lint, build, typecheck, knip, unit, cross-platform e2e, and published-runtime jobs.
Three things the next person running this suite should know, each of which currently fails late rather than up front (#702). It needs ANDROID_HOME for the Android row, or the run spends 37s inside a Gradle build before reporting SDK location not found. It needs LANG/LC_ALL set to a UTF-8 locale, or pod install fails inside the iOS row on a unicode normalization error. And it needs no orphaned .avd directory for the AVD names it will claim, a state that is invisible to emulator -list-avds and therefore to Stim's own check (#710). The suite's own exit status is trustworthy: it exits 1 on a failed check, a missing check, and a fatal, including one raised by its own cleanup.
1.0.0
New
- The CLI is now published as
stim. All five packages move together to the first stable release, 1.0.0. 256516b0ff - Select a shared Xcode app scheme with
ios --scheme <name>. Explicit schemes use separate build directories and cache keys, and resolve the actual product even when its name differs. 33ecb66ff9 - Reset one app's Metro transforms and file-map state with
start --reset-cache, preserving other apps and native build caches. 7cc5276586 - Each monorepo app reads its own committed
.stim.jsonbeside its package manifest. App-relative profiles and cache-provider paths resolve from that directory. c9fb9568a4
Fixes
-
Android's unverified-launch remedy uses the resolved launcher activity or dev-client URL to restart the app. Printed URLs preserve query parameters through both host and device shells. f2dd053af5
-
Android launch previews compact nested Metro error JSON into a readable module diagnosis, code frame and import stack. Previously unseen context is retained;
logsand JSON preserve full captured evidence. 912b8fd863 -
Doctor warns about effective multi-architecture Debug simulator settings, including generated Pods and imported configuration. It reports but does not rewrite project policy. 7afb23c8da
-
Doctor identifies linked native libraries whose Git metadata enters the fingerprint and prints narrow ignore entries for projects that do not build from that metadata. 89e9280f0c
-
Android workspaces with the same app-directory name receive distinct owned emulators instead of failing on a name collision. Existing device assignments are preserved. 529f7c68f2
-
Bare iOS reload failures include an exact-app relaunch fallback when no Reload control is visible, preserving the automation session, simulator and Metro port. The remedy warns about losing in-memory state. 71484ba0ae
-
iOS resolves a listed app scheme from the static
app.jsonname when a differently named workspace and multiple schemes make the existing selection ambiguous. The error distinguishes a naming mismatch from an unshared scheme. 5aa719fcfd -
iOS simulator launches allow up to 60 seconds for CoreSimulator to respond, preventing the previous 10-second deadline from aborting a slow successful launch. App readiness and crash checks remain unchanged. 8221162342
-
A workspace waiting for a shared native build no longer reports failure just because native preparation changed the cache key. It retains the wait result after validating the artifact under its own final key. fd65de16ec
-
stim worktree warmcopies ignored state directly into the destination, avoiding the extra staging and publication passes. Same-volume APFS copies request copy-on-write; missing paths are copied and existing entries are kept. 49ae2fce16 -
Doctor checks the actual source and destination volumes when reporting worktree-copy performance. 49ae2fce16
Docs
- Refresh startup-crash benchmark results and keep timeline endpoint circles visible. 2d192f66e7, 591df79435
- Document that warming must finish before editing, installing dependencies, starting Metro, building, or running another warm in the destination. Concurrent destination writes are unsupported. 49ae2fce16
Migration notes
- Replace
npx stim-cliwithnpx stim. For global installations, uninstallstim-clibefore runningnpm install --global stimso the old and new packages do not compete for the same executable. The command remainsstim; the four@stim-cli/*companion package names are unchanged. - Keep worktrees on the source checkout's filesystem for copy-on-write warming.
STIM_TMPDIRdoes not relocate this direct copy. Wait for warm to complete before writing to its destination. - In monorepos, move repository-root runtime settings into each relevant app's
.stim.json; apps no longer inherit that root file. Keep repository-wideworktree.excluderules at the main checkout root. Machine-level project and repository overrides retain their precedence.
1.0.0-rc.23
Fixes
- Launch errors show compact, labeled error, component, and native stacks with up to 10 frames each, prioritizing app code. Every
stim logsquery retains full captured stacks. be15388437, dfd342114e - Resolve JavaScript stack locations through the workspace's Metro and preserve the symbolicated context for later queries. Capture attributable native crashes on iOS simulators and Android, combine duplicate reports, and preserve Java cause chains. Native symbolication uses matching symbols where available. dfd342114e
- Show a warning when the native app stays alive with a JavaScript launch error, and explain when a delayed iOS crash report needs another logs query. Launch JSON and exit-code contracts remain unchanged. dfd342114e
Docs
- Clarify stack previews, full captured logs, symbolication limits, and delayed native crash reports in the version-matched guides and website. dfd342114e
QA
Expedited RC authorized on 2026-09-09. Omitted locally before the version commit: format:check, lint, typecheck, knip, unit tests, test:e2e, test:runtime, and the pre-tag native/manual matrix. Retained before the version commit: candidate build, CLI version/help/guide, and tarball inspection. Retained after push: exact-release-commit CI, including its format, lint, build, typecheck, knip, unit, cross-platform e2e, and published-runtime jobs.
Pre-merge evidence includes JS errors on iOS and Android, Swift fatalError,
Java IllegalStateException, JNI abort, and repaired app launches on both
platforms. Captured command output.
This evidence predates the version bump; the full native matrix is not rerun
against the release candidate. Physical and remote devices were not tested.
1.0.0-rc.22
New
- Recycle compatible Android emulators across worktrees. Adoption clears app data, retains matching APKs to skip installation, and recovers from signer or version conflicts. The default pool holds three emulators. ea0292b1
- Wait for optional
[stim:readiness] pendingandreadydevice logs during launch, and report whether readiness completed, timed out, or failed. 689ad047
Removed (breaking)
- Remove the legacy
caches.injectMetroStoremachine setting. Useoptimizations.metroSharedCacheinstead. 2c585854
Fixes
- Wait for Metro to deliver the bundle before starting launch-stability checks. a4832aa3
- Align Android device-log timestamps with the host clock so launch filters include the current app's logs. dd0f2c5e
- Resolve dynamic imports from nested modules when bare Metro uses a hoisted
metro-runtimepackage. 1fe471f5 - Restart a stuck Android app when following the unverified-launch remedy. 6230e4aa
Docs
- Document build optimizations, Android CAS, emulator recycling, and optional readiness reporting. Refresh the website and publish activity-timed Android and iOS launch-error benchmarks. 2c585854, 1f9fc1e8, c2f848b4
Migration notes
- Replace
caches.injectMetroStore: falsewithoptimizations.metroSharedCache: falseto keep shared Metro caching disabled. - Set
pool.androidParkedMaxto0when a fresh emulator is required. Recycling preserves system apps, shared storage, accounts and device settings; app data is cleared at adoption. Older AVDs without recorded creation settings are deleted when removed.
QA
- Full preflight: formatting, lint, build, typecheck, knip, 3,737 unit tests, 66 end-to-end tests, Node 20.19.4 runtime checks, website build, and all five packed tarballs passed.
- Native loop and cache suites passed for bare React Native and Expo on both iOS and Android. Both device-pool suites passed.
- Manual QA covered readiness success, timeout and error; nested dynamic imports; healthy, broken and repaired logs; UI interactions; and Trailhead on both platforms, including native cache reuse across worktrees.
- Android release QA verified current JS in the installed, signed and aligned APK after adopting an emulator with a differently signed app.
- A newly booted Android emulator can initially return
unverified. Follow the printed restart remedy and verify the UI. The recovery instruction was corrected during this QA pass. iOS may require the printed UI Reload fallback when Metro cannot identify its peer. Earlier client errors remain visible until the next launch, as documented. - Authenticated remote providers, physical-device signing, Android flavors, and iOS release swapping were omitted because those implementations did not change. Shared launch and lease behavior has simulator/emulator and unit coverage.
1.0.0-rc.21
Fixes
- Persist exact process identities for Stim-owned supervisors, log collectors,
and managed tunnels instead of inferring host-process ownership through
ps. Useunique-pid@0.1.1so permitted process inspection works in macOS sandboxes that deny boot-session metadata. Cleanup retains unverified records and leaves replacement processes and their device claims alone. dfaeae32
Removed (breaking)
- Remove
stop --force. Stim no longer terminates externally started Metro servers or infers process ownership from a port, command name, or directory. dfaeae32
Migration notes
- Stop a still-running legacy supervisor manually before starting a replacement when its saved state has no verifiable process token. Externally started Metro remains reusable but must be stopped by its owner.
- Process checks and signals are separate OS operations. On macOS, a token captured without boot identity cannot distinguish a repeated PID and exact start timestamp after a reboot or clock reset. Denied process inspection is not treated as proof that a process exited.
QA
Expedited RC authorized on 2026-09-07. Omitted locally before the version commit: format:check, lint, typecheck, knip, unit tests, test:e2e, test:runtime, and the pre-tag native/manual matrix. Retained before the version commit: candidate build, CLI version/help/guide, and tarball inspection. Retained after push: exact-release-commit CI, including its format, lint, build, typecheck, knip, unit, cross-platform e2e, and published-runtime jobs.
The included PR passed independent review, 3,602 unit tests, 66 end-to-end tests, and required CI before merge. Forty collector-ownership and reclaim tests also passed inside the actual Codex sandbox with the published library. These checks are not a substitute for the omitted candidate native/manual QA.
1.0.0-rc.20
When Stim is not installed globally, replace stim with npx stim-cli.
New
- Configure build optimizations with machine defaults and repository/project overrides: artifact and remote caches, release bundle swapping, Metro sharing, iOS compilation caching and prefix mapping, and Android compiler cache, PCH, Gradle cache, and target ABI settings. See
stim guide settings. a4e2251 - Opt into experimental Android PCH caching across worktrees using Apple Clang CAS on macOS. This requires a manually prepared compatible compiler/NDK toolchain; see the Android CAS report for setup, evidence, and compatibility limits. f45d9e2
Fixes
- Distinguish harmless executable lookups from actual device commands when auditing benchmark isolation, while retaining checks for real invocations. a563dad
Docs
- Complete the published Android benchmark coverage and omit private inventory data from benchmark exports. df12e17
Migration notes
- Shipping defaults are unchanged: Stim-supplied Android ccache disables PCH. CAS is a mutually exclusive opt-in through
optimizations.android.compilerCacheand a toolchain manifest. - Android compiler/PCH modes use separate generated CMake profiles so switching backends does not retain a previous compiler. Old profiles accumulate; worktree removal reclaims them.
- CAS is experimental. Automatic toolchain installation, broader project compatibility, and CAS eviction/diagnostics remain unfinished.
QA
Expedited RC authorized on 2026-09-07. The pre-tag native and manual QA matrix was omitted. The candidate passed formatting, lint, build, typecheck, knip, all 3,664 unit tests, all 66 E2E tests, the runtime check, CLI version/help/guide checks, and inspection of all five tarballs. Publishing remains gated on CI for the exact release commit, including Node 22, Node 24, and the published Node 20.19.4 runtime.
1.0.0-rc.19
When Stim is not installed globally, replace stim with npx stim-cli.
Fixes
- Enforce benchmark runner filesystem isolation for coordinator data and sibling runs, with verified access checks before timing starts. 5751260
- Correct benchmark auditing of quoted shell commands and separate crash launch/error capture. e2cb47a d53bebf
Docs
- Require worktree warming to finish successfully before starting Metro, building, or installing dependencies. A shell job ID is not completion. 35a77e4
- Refresh validated Android and launch-recovery benchmark results on the website. e62e6f7
QA
Expedited RC authorized on 2026-09-06. Omitted locally before the version commit: format:check, lint, typecheck, knip, unit tests, test:e2e, test:runtime, and the pre-tag native/manual matrix. Retained before the version commit: candidate build, CLI version/help/guide, and tarball inspection. Retained after push: exact-release-commit CI, including its format, lint, build, typecheck, knip, unit, cross-platform e2e, and published-runtime jobs.
1.0.0-rc.18
When Stim is not installed globally, replace stim with npx stim-cli.
Fixes
- Warming an Android worktree excludes generated autolinking caches, including nested apps, so Gradle resolves native dependencies inside the new checkout. 1fac645
- Keep large temporary copies on the operation's volume, including worktree warming and cached app preparation.
STIM_TMPDIRor machinetempDiroverrides placement; doctor reports cross-volume copies. 20232f3 - Repair stale Android CMake launcher configurations with
stim doctor --fix --platform android, including installed native modules. Stop all native builds before repair; custom launchers and shared ccache entries are preserved. 008d057 - Validate benchmark cache entries with the pinned CLI's fingerprint library. 0079fb3
- Audit benchmark device-session scoping per shell command, accepting correctly scoped delayed navigation and rejecting unscoped invocations later in a chain. 782b8c5
Docs
- Explain the workflows where Stim adds little value on the homepage. fb82b23
QA
Expedited RC authorized on 2026-09-06 after disk pressure paused the native matrix. The product candidate passed formatting, lint, build, typecheck, knip, 3,605 unit tests, 66 end-to-end tests, runtime smoke, and tarball inspection. Real Gradle autolinking regeneration and CMake launcher repair were verified. Native bare Android and bare/Expo iOS workflow checks passed, as did the bare iOS cache suite.
Omitted before the version commit: the remaining native cache matrix, Expo Android workflow, and manual Release-artifact checks. Final candidate build, version, help, guide, and all five tarball inspections are retained, together with blocking CI on the exact release commit, including Node 22, Node 24, and the Node 20.19.4 published-runtime job.
1.0.0-rc.17
If Stim is not installed globally, replace stim below with npx stim-cli.
Fixes
- Android builds disable CMake precompiled headers by default when Stim supplies ccache, improving compiler-cache reuse across worktrees. Explicit project PCH settings are preserved; builds without Stim's ccache setup and iOS builds are unchanged. The tradeoff is slower cold C++ compilation. (9418d3d)
- The repository's benchmark harness flags missing or below-target Android compiler-cache evidence, retains failed attempts for investigation, and excludes them from published comparisons. Android preparation and preflight also require a doctor report without cost findings. (872bb1f)
Docs
stim guide agentkeeps the workflow, safety rules, and recovery actions up front. Optional cache warm-up, ABI selection, fingerprint fallback, and build-sharing details live in the build and concurrency guides. (1c420aa)
QA
Expedited RC authorized on 2026-09-06. Omitted locally before the version commit: format:check, lint, typecheck, knip, unit tests, test:e2e, test:runtime, and the pre-tag native/manual matrix. Retained before the version commit: candidate build, CLI version/help/guide, and tarball inspection. Retained after push: exact-release-commit CI, including its format, lint, build, typecheck, knip, unit, cross-platform e2e, and published-runtime jobs.
1.0.0-rc.16
If Stim is not installed globally, replace stim below with npx stim-cli.
New
stim doctorreports the running CLI version, the firststimexecutable resolved fromPATH, and every distinct Stim installation found onPATH. It flags the setup when the resolved executable is older than the CLI currently running. (0070bae)
Fixes
- The benchmark harness accepts ABI-scoped Android APK cache keys when validating a native-change run. (93bfeb1)
- Benchmark run guards reject the wrong Stim version inside the timed shell, enforce inactivity and hard timeouts across process trees, and preserve failures hidden inside shell chains. (e3410c2)
Docs
- The release guide defines an explicitly authorized expedited lane for release candidates, including the checks it may omit and the package, CI, tagging, and publishing safeguards it must retain. (5da0e95)
QA
Expedited RC authorized on 2026-09-05. Omitted locally before the version commit: format:check, lint, typecheck, knip, unit tests, test:e2e, test:runtime, and the pre-tag native/manual matrix. Retained before the version commit: candidate build, CLI version/help/guide, and tarball inspection. Retained after push: exact-release-commit CI, including its format, lint, build, typecheck, knip, unit, cross-platform e2e, and published-runtime jobs.
1.0.0-rc.15
If Stim is not installed globally, replace stim below with npx stim-cli.
New
stim worktree warmcopies missing ignored dependencies, native output, and local configuration from the main checkout into an existing linked worktree. Existing entries and tracked files are preserved. (6412169)stim reload [ios|android]reloads JavaScript on a running owned simulator or emulator without rebuilding or reinstalling the app. It reports the requested reload, selected device, Metro port, and strategy; app-side completion still needs verification. (43da17e, 24d6d71)- Sectioned guide topics support
stim guide <topic> <section>, including direct error-code lookup such asstim guide errors STIM_NO_METRO. (d377e92) - Owned Android emulators use Quick Boot where supported. Shutdown waits for the emulator and snapshot save to finish, and uncertain process locks prevent deletion. Displayless Linux keeps snapshots disabled. (e6ddc3a)
Removed (breaking)
worktree create,--carry-ignored, and creation-only settings are removed. Use Git to create worktrees, then runstim worktree warmwhen you need ignored files from main. Tracked edits are no longer copied. (834b338)
Fixes
- Warming preserves APFS clones through file publication on macOS, avoiding full data copies while keeping existing files safe. Files remain independent of the main checkout. (0a5c2fa)
- Benchmark overview links open the selected platform's audit, including Luna's Android JavaScript result. (5d3aced)
- Android shutdown flushes guest writes before killing the emulator, with a five-second limit so a stalled flush cannot block cleanup. (3abbb87)
- iOS skips cache lookup and storage when the final fingerprint cannot be computed, preventing an artifact from being stored under its earlier key. (74d9a3e)
- Commands waiting for a native build keep waiting when another process takes over a failed builder's lock. (70f3108)
- Android Debug builds target the selected device's ABI and route eligible CMake compilation through ccache. (0e5eb6d, b9743cd)
start,ios, andandroidreject directories that are not React Native or Expo apps;doctorreports the problem as a finding. Invalid relative registry paths are reported as invalid records, and worktree Git commands pass paths as arguments. (360d871, c93997f, b7100dd)
Docs
-
Native cache QA uses a normally built disposable source checkout and separate empty test caches. (8865207)
-
The agent workflow verifies UI changes on the reported device and checks logs again before cleanup. (0dff4a2)
-
Error-guide headings match emitted messages, and shared-lock guidance covers the records those locks protect. (8576b5e)
-
Setup, platform requirements, device ownership, and reload guidance now agree across the CLI and website. A successful log query is distinguished from a clean log result. (b264ffa, 0ab7174, cc34d57)
-
The skill description names common agent tasks while retaining its single version-matched guide command. (82e7ac9)
-
The benchmark website has separate platform/model selectors, visible unavailable combinations, and published Android results. (ebe5359, 9c54814, 8fde8cc)
Migration notes
stim worktree create and --carry-ignored are removed. Create the checkout with Git, then warm it from inside the new directory:
git worktree add -b my-feature ../my-feature HEAD
cd ../my-feature
stim worktree warm
If an agent harness already created the worktree, run only stim worktree warm. Warming never applies the main checkout's tracked edits. Remove worktreeDir, worktree.baseRef, worktree.include, and .worktreeinclude from project setup; worktree.exclude and .worktreeexclude still control warming. stim worktree remove works with warmed and unwarmed worktrees and retains Git-created branches.
stim guide errors, lifecycle, facts, and cleanup now print a section index. Add the section name to read its full guidance. The agent, Metro, logs, and settings topics remain whole. (d377e92)
The website build retains two accepted high-severity image-size denial-of-service advisories: GHSA-5p2g-fcmc-qvqq and GHSA-w3rx-r6r6-pgpr. A malformed local image can hang a website build. The audit exceptions do not fix the parser; published Stim packages do not depend on it. Docusaurus tracks a replacement in #12231.
The remaining native-device validation continues after this candidate is published.
1.0.0-rc.14
New
a19b03aadds a separate JavaScript launch-crash benchmark that measures diagnosis and recovery without mixing those results into readiness charts.ea416bfadds audited Android agent benchmark results alongside the iOS results.c69d4aeadds simulated prompt responses to the benchmark website.
Fixes
bdd19a6includes available Expo and bare React Native code frames and stacks in humanstim logs --errorsoutput. Launch errors now distinguish a live native process from an exited process so agents can avoid unnecessary native rebuilds; an exited process still needs another native launch.
Docs
- The benchmark presentation now preserves terminal output, follows timeline playback, uses clearer event labels, aligns interaction video, and improves prompt-card spacing (
573546d,07f670a,52f486d,0d56b35,9b1f822,f660636). a41b6cbupdates the documented cache-savings examples to match measured results.8ae44bdpublishes the strict Sol iOS benchmark report.
1.0.0-rc.13
New
91724f4structures successfulstim doctoroutput into labeled project, platform, shared-service, and automatically handled checks, and adds the selected platform to reports with findings.f06cd45addsstim guide agentas the version-matched coding-agent workflow. The skill installed bynpx skills add appandflow/stimis now a tiny one-time router to that CLI guidance.
Fixes
58140dcremoves dangling separators when Stim shortens long owned iOS simulator names.
Docs
- The benchmark site now includes an overview, interactive timeline, model selection, playback, valid-attempt filtering, run activity, zoom, deep links, grouped background lanes, Safari gestures, corrected device-session evidence, and clearer result labels (
efb389d,198c996,ff52efc,0ba0db6,fbadca6,7ab80fa,e6d8040,47802ba,f2c2752,7689578,d91a97e). 82855e0adds copyable prompts for common build, device, log, cache, and PR-validation workflows and clarifies when agents should use Stim or create a worktree.3390c6fadds an automated continuous benchmark replay for the website and social sharing, including the audited clock and recreated real-speed device interaction.
1.0.0-rc.12
Fixes
98480e9maps slash-separated worktree names to flat+paths instead of percent-encoded%2Fpaths, avoiding nested Xcode build failures while preserving the Git branch hierarchy.
Docs
4f1e3bddefines the benchmark v4 readiness protocol for separate JavaScript and native passes, exact device evidence, failure arms, and timeline artifacts.
Migration notes
Existing rc.11 %2F worktrees are not migrated. Continue or remove them from their current checkout; newly created slash-named worktrees use +.
1.0.0-rc.11
New
stim doctor --platform ios|androidkeeps shared checks while filtering native findings and fingerprint parity to the selected platform. Android-only checks no longer invoke Xcode tooling (901b561).
Fixes
stim worktree createaccepts safe slash-separated names such asbench/run-1, preserves the hierarchy in the Git branch, and derives a collision-resistant single-directory checkout and device label (5b17558).
Docs
- Documented the v3 agent benchmark matrix across JavaScript and native changes, Stim and direct-tool control arms, and supported coding models (4a1bc15).
Migration notes
- Existing
stim doctorand slash-freestim worktree createinvocations keep their current behavior.
1.0.0-rc.10
New
- Owned iOS simulators can be parked when a worktree is removed and adopted by
a later matching workspace. Adoption resets app data, privacy grants,
keychain state, and other user apps; ownership and cleanup remain fail-closed
across crashes and concurrent garbage collection.
statusandgcexpose the bounded pool, whose machine default is three simulators (9136251). stim statsaggregates build timing and cache effectiveness without changing the build lifecycle (4d0cabb).- iOS runs can select an installed simulator with
--device-typeand--runtime; Android runs can select an installed image with--system-image(c29c6bd).
Fixes
- iOS fingerprints are computed while the simulator boots, and a simulator booted by the current run is not redundantly listed again (9dbba71, e66bde6).
- Long native builds emit phase-named heartbeats sized from the project's last cold build instead of a fixed interval (91a1d6e).
- Worktree, server, device, and platform-run output uses one consistent phase vocabulary (e759551, 1daa954, b91ca0a).
- iOS simulator log collection anchors its process predicate to the installed app executable path, avoiding matches from unrelated processes (b3915e0).
- Workspace overrides move
uuid,serialize-javascript, andqsto patched dependency versions (029854b).
Docs
- Added the simulator-pool and aggregate-statistics designs, and documented the stacked pull-request workflow used for dependent changes (d8de9c8, 7ebed3e, d017f3c).
Migration notes
- No command migration is required. Projects that require a completely fresh
simulator system image for every worktree can set
pool.iosParkedMaxorSTIM_POOL_IOS_PARKED_MAXto0.
1.0.0-rc.9
Fixes
- Android builds are re-fingerprinted after Gradle succeeds, so inputs changed
by Gradle or autolinking store the artifact,
lastBuild, and JSON cache identity only under the post-build key. If re-fingerprinting fails, Stim installs the APK without caching it or reporting a stale cache identity (2b689ad). - Dev-client launches carry Expo's session-only
disableFab=1parameter on iOS and Android. Expo versions that support the parameter hide the floating Tools button without changing the user's persistent preferences; older versions ignore it (7c4dd23). stim android --jsonincludesdurationMs, matching the documented iOS payload and reporting the wall time of the complete successful native run (adc927e).- A warm iOS run reports the installed-app identity proof separately from Expo dev-client preference preparation, so a slow simulator command is attributed to the step that actually ran (e858950).
1.0.0-rc.8
New
stim ios --device [udid]builds theiphoneosslice, selects a cabled iPhone, installs and launches it through devicectl, and points a Debug build at this workspace's Metro over the LAN. The phone is used, never owned: the signing gate reads the bundle's ownembedded.mobileprovisionand refuses one that does not name the target device, and Stim re-signs only copies it makes (02cfcb2, 43fd760).stim logs --source devicecollects a phone's runtime logs through the devicectl console. That stream renders every os_log level alike, so a line staysinfounless a crash marker or devicectl's own error prefix proves otherwise; severity is never guessed (1b79cb2).- A launch blocked by the phone's pending Local Network permission is named as such, and the remedy says how to accept the prompt, including from a device tool, because the grant cannot be pre-seeded from this machine (a2b3ee9).
stim device lock <ios|android> [id] --for <duration>holds a connected physical device across runs, from 10 seconds to 30 minutes, waiting for another workspace the way a run does.stim device unlock [platform]gives back one platform's lease or every lease this workspace holds (33141aa).ios --deviceandandroid --devicetake a run-scoped lease from just before the install until the run exits, raising the expiry at each device step so a killed run frees the device in bounded time.--wait <seconds>waits for a device another workspace holds;--no-waitproceeds without a lease and says what that costs.statuslists leases,stopandworktree removerelease this workspace's, andgc --deletedeletes expired lease files (3fbb460, 96f3c1f).--devicewith no id picks from the connected pool: the device this workspace already leases when it is present, otherwise the first free one in case-folded id order. A leased device that is not connected refuses rather than moving silently to another phone (eb02f1a).doctorreports the missing sandbox allowance when a write toSTIM_HOMEactually fails, naming the absent keys, anddoctor --fixapplies the findings Stim can repair itself. It writes only per-user files, refuses a settings file it cannot read back, and refuses under Codex, where the only value that clears Stim turns the sandbox off wholesale (6640771).stim worktree create --dir <path>places one worktree where the run needs it, taking precedence over theworktreeDirsetting; a relative--dirresolves against the current directory, and a blank one is refused (fdb60b7).
Fixes
- A run summary says what it proves: a verified launch no longer prints the
iOS connection refusal it already recovered from, the ready line reads
bundle loaded, process alive, stable for 3s -- the first screen may still be rendering, the heartbeat holds its 30-second grid without repeating a beat, and preparing an owned simulator or emulator gets its own timed line (3c86cfc). - Every known setting is type-checked from one shape table in
start,ios,android,worktree create,gc, anddoctor, and a known key is no longer also warned about as unknown. Behavior change: a non-stringcache.provideris refused before the build instead of reported while the run continues (0c7c691, 278ce61). worktree createjudges carried Pods against thePodfile.lockthe new worktree ends up with, not the one it started from (c006de1).worktree createrolls back a branch it stranded, refuses--baseon a branch that already exists (d60db78), and runsgit worktree addunderLC_ALL=Cso the branch-exists guard matches git's English text on a build with translation catalogs (891e94f).- A relative
worktreeDirsetting resolves against the settings root instead of the process's working directory (0a0603f), and a non-stringworktreeDiris refused instead of crashing (af1e2d3). - A gradle failure whose report is interleaved with task output surfaces the real cause chain instead of the surrounding tail (c1bc9ba).
- An owned Android emulator's console port is chosen and recorded under the
global config lock before boot, so concurrent workspaces cannot claim the
same serial. A failed boot releases only its matching port claim while
keeping the owned AVD recorded for
gc(e6535b2). - The fingerprint ignores
android/local.propertiesandandroid/.idea, machine state no native build reads, so a cache key can match across checkouts (060aeb2). stop,gc --delete, andworktree removeprove a collector pid is this workspace's collector before signalling it, and report a pid they cannot prove instead of killing a process that reused it (d960785, f373e95).- A pool refusal gives each connected device's own reason -- Developer Mode off, unpaired, unreadable lease -- instead of asking which of several to name (15f994c).
stop --json,status --json, anddoctor --jsonprint their payload on exactly one stdout line, matching the documented contract and the other single-payload JSON commands (daaaeeb).- Release: every published tarball carries real inter-package versions,
substituted by pnpm at pack time rather than bumped by hand
(c96df6b), and the
workflow computes its publish dist-tag, so a candidate cut after a stable
release lands on
nextinstead oflatest(9ef253c). - The Expo dev menu stays off an app Stim launches, and on iOS so does its
floating Tools button. The dev-client project url carries
disableOnboarding=1on every launch, a phone launch passes-EXDevMenuShowsAtLaunch 0and-EXDevMenuShowFloatingActionButton 0as launch arguments through devicectl, the simulator preference write covers the Tools button, and Android adds theEXDevMenuDisableAutoLaunchintent extra; the simulator's onboarding preference write is gone. Android's floating button still shows (#249) (4d432f4, 21ead76).
Docs
- The website documents
doctor --fixand doctor's sandbox finding (8977ff0). guide errorsand the skill name the three things an agent sandbox blocks -- writes toSTIM_HOME, the simulator service, the adb server -- and what to allow, instead of leaving an agent to find out by failing (4916eaf).
1.0.0-rc.7
New
stim iosandstim androidskip the install entirely when the device already holds the exact artifact, going straight to launch (c07f97b).- A flavored Android project with no configured variant is refused before gradle runs, naming the candidates, instead of after a full assemble (168528b).
Removed (breaking)
- The stale-APK mtime refusal is gone: an APK older than the build that just ran is no longer treated as foreign (6941c5d).
Fixes
- Android installs use the APK's real applicationId instead of the gradle namespace, so suffixed and flavored ids launch correctly (a4b64d7).
- The log collector signals its stream through the child handle rather than a pid that can be reused (f342663).
- The real-xcodebuild diagnostic test carries its sibling's timeout instead of flaking at the 5s default (425cd13).
1.0.0-rc.6
New
stim android --device [serial]installs and launches on a connected physical Android device after the usual build. With no serial it uses the one connected device and refuses with the candidate list when adb reports several; it cannot be combined with--remote. The device is used, never owned: nothing is recorded, andstop,gc, and teardown never touch it (518965a).- CocoaPods runs through bundler when the project pins it: a
Gemfile.lockthat resolves a cocoapods spec routespod installthroughbundle check --dry-run->bundle install(when gems are missing) ->bundle exec pod install, all frozen so the tracked lockfile is never edited. A Gemfile without a lock, a lock without cocoapods (fastlane-only), or a machine withoutbundlekeeps today's plainpod install. Doctor, worktree warnings, and the Xcode sandbox remedy share the same rule (0c636f6).
Removed (breaking)
stim gc --delete --allis nowstim gc --delete --cache all: the flag reaches caches only and its name says so (8e4eee1).
Fixes
- Android debug builds reverse port 8081 only when the
debug_http_hostwrite fails, so the reserved-port wiring is not undone by a fallback that was not needed (531833f). - A damaged Xcode compilation-cache object is named as the build-failure cause, with the cache path to clear (1fbde68).
- CI: vitest 5 stops spurious worker-exit failures (1e20a7d).
Docs
- The cache e2e gate judges Gradle storing by entry names, accepts a
warm-machine cache as engagement, and isolates
GRADLE_USER_HOMEso the storing leg is always measurable (86f9273, bf4640a); the bare fixture installs its pods through bundler (d8b6fa7).
1.0.0-rc.5
New
@stim-cli/cache, a fifth published package holding the cache provider contract and the local-first tier coordination behind the native artifact cache and the Metro transform cache (fadc2ce). The built-in filesystem provider is the only tier shipped; a remote provider becomes a pure addition that implements the same capabilities. On-disk cache layout, keys, APFS clone behavior, andgcreporting are unchanged, and a project with no provider configured loads nothing new.cache.providerandcache.optionssettings select an optional second-tier provider through the existing settings layers. A committedcache.provideris executable code and is documented as such. Provider calls are bounded: load, resolve, and upload each carry a timeout with aSTIM_CACHE_*_TIMEOUT_MSenvironment override, Metro reads are concurrency-capped, and repeated failures disable the tier for the run with one warning.- Contract-test helpers for provider authors ship in
@stim-cli/cache, including checks for abort handling,overwritesemantics, and destination hygiene, runnable against a provider module soapiVersionand factory validation are exercised.
Fixes
- A slow first simulator boot no longer fails
stim ioswith a rawspawnSync /bin/sh ETIMEDOUT. Stim re-enterssimctl bootstatuswhile the device still reports Booting, against a ten-minute deadline, and a failure names the udid and the wait (91900c4). - The warm-carry summary no longer flips its dependencies verdict when a later
node_modulesentry lacks a siblingpackage.json(bc155c1). stim doctorclaims the fingerprint-parity check only where it runs (a checkout without installed dependencies), and its expensive main-checkout probes are pinned behind their preconditions (400e068).- CI: the native e2e workflows read the renamed
STIM_*environment variables again, restoring the cross-run build cache they silently lost in the rebrand (a49f7e5).
Docs
stim guide errorsdocuments every refusal code the CLI emits, enforced by a contract test that scrapes the engine sources (7c2ff5c).- The website documents the real carry exclusions, the real guide topics, the
parity check's precondition, and the two shapes of the
cachessetting (7f32bf2). - The CLI and docs carry the Stim rebrand and the redesigned website (cc9276b).
1.0.0-rc.4
This release candidate improves fresh-agent app launches. It also helps agents prepare the main checkout before they create native worktrees.
Install and run
npx skills add appandflow/stim
npx --package=stim-cli stim doctor
npx --package=stim-cli stim start
npx --package=stim-cli stim ios # or: npx --package=stim-cli stim android
npx --package=stim-cli stim logs --errors
npx --package=stim-cli stim stop
Self-contained app launch
stim ios and stim android build or restore the app, install it, launch it,
and check its readiness. The Stim skill does not require a separate device
automation tool for this workflow.
The iOS launch path sets the workspace Metro address before it opens the app. The Android launch path injects the Metro preferences and uses the launcher activity when a dev-client URL is not available. These paths reduce Expo dev menu and deep-link confirmation friction.
Stim observes the app for three seconds after Metro completes the bundle. The final summary reports the process state, launch errors, device ID, app ID, Metro port, cache result, and log path.
Main checkout diagnostics
stim doctor checks the main checkout when it runs from a linked worktree. It
reports stale dependencies, CocoaPods state, native output, build-cache state,
and local commits that are missing from the upstream branch.
The skill asks agents to run doctor before native work. It also recommends one
main-checkout build when more native worktrees are expected. A later
stim worktree create --carry-ignored can clone that prepared state with APFS
copy-on-write support.
Release reliability
The release workflow retries npm registry checks after publishing. This avoids a false release failure while a new package version propagates through the registry.
All four packages use version 1.0.0-rc.4 and are released together.
1.0.0-rc.3
This release candidate adds optional SimSlim profiles for Stim-owned iOS simulators. It also prevents ignored-file carry-over from copying a nested Git worktree into a new worktree.
Install and run
npx skills add appandflow/stim
npx --package=stim-cli stim doctor
npx --package=stim-cli stim start
npx --package=stim-cli stim ios # or: npx --package=stim-cli stim android
npx --package=stim-cli stim logs --errors
npx --package=stim-cli stim stop
Optional SimSlim profiles
Projects can commit a SimSlim profile and select it in .stim.json:
{
"ios": { "simslimProfile": ".simslim/dev.json" }
}
Install SimSlim once on each Mac with
brew install mobai-app/tap/simslim. SimSlim requires an iOS 18 or newer
simulator.
stim ios applies the profile only to its owned local simulator. The first
change can reboot the simulator. Later matching runs are fast no-ops in
SimSlim. Removing the setting restores stock services when Stim applied the
profile.
Stim validates the profile path before it creates or boots a simulator. The
path must stay inside the repository. stim doctor reports an invalid profile
or a missing SimSlim installation, including for apps inside a monorepo.
Safer ignored-file carry-over
stim worktree create --carry-ignored now reads Git's registered worktrees and
excludes them from the copied files. This prevents nested Claude, Codex, and
custom worktree locations from being copied into the new worktree.
The check uses Git's worktree registry, so it does not depend on a hardcoded
directory list. If Git reports only an ignored parent directory, Stim excludes
that full parent. Project-specific exclusions remain available through
.worktreeexclude and worktree.exclude.
All four packages use version 1.0.0-rc.3 and are released together.
1.0.0-rc.2
This release candidate improves the full agent workflow. It makes worktree creation more predictable, reports native cache activity, and gives launch errors a clearer severity.
Install and run
npx skills add appandflow/stim
npx --package=stim-cli stim doctor
npx --package=stim-cli stim start
npx --package=stim-cli stim ios # or: npx --package=stim-cli stim android
npx --package=stim-cli stim logs --errors
npx --package=stim-cli stim stop
The platform command builds or restores the app, installs it, opens it, and checks its launch state. Plain output streams progress and ends with the full device, app, Metro, cache, and log facts.
Worktree workflow
stim worktree create <name> now uses the current checkout as its base. Use
--base fresh to start from origin/HEAD, or use --base <ref> for a branch,
tag, or commit.
The command reports which ignored dependencies, CocoaPods files, and native build output can make the new worktree warm. It also finds nested lockfiles and prints the correct install command and directory.
stim worktree remove removes a Stim-owned branch only when that branch has no
unique commits. It keeps the branch when another worktree uses it or its ref
changes during cleanup.
Native cache reporting
The iOS command now reports Xcode compilation-cache hits from Xcode 26. This metric stays separate from the Stim artifact-cache result. The output explains when the Xcode metric did not run or was not available.
Launch diagnostics
The iOS log collector reduces known harmless UIKit focus messages. It also keeps a TCP refusal unless a later log record proves that the same connection succeeded. The raw log timeline remains complete.
The release also fixes a Node 22 Linux test race in tunnel cleanup.
All four packages use version 1.0.0-rc.2 and are released together.
1.0.0-rc.1
This release candidate makes the normal agent loop easier to follow and more reliable. Plain output now streams each phase and finishes with the complete device, app, Metro, cache, and log facts. JSON output remains available for scripts that need a stable payload.
Install and run
npx skills add appandflow/stim
npx --package=stim-cli stim doctor
npx --package=stim-cli stim start
npx --package=stim-cli stim ios # or: npx --package=stim-cli stim android
npx --package=stim-cli stim logs --errors
npx --package=stim-cli stim stop
Launch readiness
stim ios and stim android wait for Metro to finish the bundle before the
launch stability window starts. The commands then observe the app for three
seconds and check that its process remains alive.
The final output reports whether the app is ready, still bundling, unverified, or failed. A Metro bundle failure or an exited app process fails the command. Errors observed during the launch window are printed for the agent to assess, even when the app remains alive.
Plain agent output
The platform commands stream the important build and launch phases as they happen. Their final plain-text summary includes:
- the full simulator UDID or emulator serial;
- the app ID;
- the Metro port and state;
- the native build cache result;
- the log directory.
The bundled skill recommends plain output for interactive agent work. It uses JSON only when another program must parse the result.
Owned simulator cleanup
An explicit stim stop shuts down a Stim-owned iOS simulator even when a
device automation session is still active. Stim verifies ownership before the
shutdown and never shuts down an unowned simulator.
This lets an agent keep its Agent Device session active through visual proof,
run stim stop, and then close the automation session.
Error reporting
Native prebuild failures now appear in stim logs --errors. Bare React Native
Metro logs also retain more launch context for diagnosis.
All four packages use version 1.0.0-rc.1 and are released together.
1.0.0-rc.0
This is the first release candidate for the stim-cli package family.
Stim gives each React Native or Expo workspace an isolated local development environment. Each workspace gets its own Metro port and its own simulator or emulator. Native builds and Metro transforms use shared caches, so parallel worktrees can reuse work without sharing runtime state.
The CLI is designed for coding agents. Commands never prompt. Status and
progress go to stderr. Commands with --json print one machine-readable result
to stdout. Failures include stable error codes, focused diagnostics, and
specific remedies.
New
One complete agent loop
npx skills add appandflow/stim
npx --package=stim-cli stim doctor
npx --package=stim-cli stim start
npx --package=stim-cli stim ios # or: npx --package=stim-cli stim android
npx --package=stim-cli stim logs --errors
npx --package=stim-cli stim stop
doctor reports project settings that can prevent correct or fast builds.
start runs the project dev server under a detached supervisor. ios and
android build or restore the app, install it, and launch it on the owned
device. logs --errors gives the agent a direct pass or fail check. An empty
result with exit code 0 means that the current error window is clean.
Isolated workspaces and owned devices
- Each project or worktree gets a reserved Metro port.
- Each environment gets a simulator or emulator named
stim-<label>. - Stim operates only on devices that it created and recorded as owned.
- Physical devices and user-created virtual devices are outside the product scope.
worktree createcreates an isolated git worktree and prints only its path to stdout.worktree removechecks git state before it removes a worktree and its environment.stopstops the supervisor and shuts down the device without deleting it.gcreports stale state. Onlyworktree removeandgc --deletedelete owned resources.
Several agents can work in parallel without sharing a bundler, device, runtime state, or project log stream.
Supervised dev servers and structured logs
start runs bare React Native Metro in process. It runs the project Expo dev
server as a child when the project uses Expo. Both modes use the workspace's
reserved port and verify server identity before reporting success.
The CLI merges bundler, client, build, and device records into one NDJSON
timeline. logs can filter by time, source, level, text, or the current error
window. logs --follow streams new records. status summarizes all managed
environments and reports active processes, assigned devices, ports, cache
results, and errors.
Shared native build cache
ios and android calculate a native fingerprint with
@expo/fingerprint. A matching artifact can be reused by another worktree.
The cache key includes the platform, build configuration or variant, and target
class.
- iOS supports
--configuration <name>. - Android supports
--variant <name>, including product flavors. - Release builds skip Metro and contain the current JavaScript bundle.
- A release cache hit receives the current JavaScript before installation.
- A failed JavaScript swap falls back to a full build.
- Concurrent requests for one fingerprint use a single build.
- A configured Expo build cache provider can act as a second cache tier.
Stim also enables Xcode's compilation cache when the installed Xcode supports it. Android builds use Gradle's shared build cache. Device boot starts in parallel with compilation.
Shared Metro transform cache
@stim-cli/metro provides a Metro transform cache shared by every worktree.
The package also provides the NDJSON reporter used by bare React Native dev
servers. Stim injects the shared store without requiring a project change
when the installed React Native or Expo version supports that path.
Cache inspection and cleanup
Cache packages register their storage with the shared cache manifest.
stim gc reports registered and detected caches without deleting them.
npx --package=stim-cli stim gc
npx --package=stim-cli stim gc --delete --older-than 30
npx --package=stim-cli stim gc --delete --all
Age-based cleanup removes independent entries. Atomic stores, such as Xcode's
content-addressed store, are emptied only with --all.
Machine and project settings
Project settings can select the iOS device type, iOS runtime, Android system image, Android build variant, worktree defaults, and project label. Machine settings can move cache roots and set optional build or device concurrency limits. Environment variables override machine cache and concurrency settings.
The CLI keeps runtime state under $STIM_HOME, which defaults to
~/.stim. The normal workflow does not require generated project files
or an initialization command.
Built-in agent guidance
The stim-cli npm package includes an agent skill and version-matched guidance.
guide prints the reference for the installed CLI version. Topics cover
facts, lifecycle, Metro, logs, errors, cleanup, and settings.
Packages
stim-cli- the command-line interface and bundled agent guidance.@stim-cli/core- shared cache paths, cache keys, locks, and cache registration primitives.@stim-cli/metro- the shared Metro transform cache and NDJSON reporter.@stim-cli/expo-build-cache- a local Expo build cache provider for Expo-driven native builds.
All four packages use version 1.0.0-rc.0 and are released together.
Requirements and limits
- Node.js 20.19.4 or later on Node 20, or Node.js 22.12.0 or later.
- Xcode and an installed iOS Simulator runtime for iOS work.
- Android SDK tools and an installed emulator system image for Android work.
- Bare React Native and Expo projects are supported.
- Store signing, distribution builds, and physical devices are outside scope.
- The package surface can change during the release-candidate cycle.
The source, package documentation, and issue tracker are available at appandflow/stim.