How warden works
Leases, owners, liveness, and which devices warden will touch.
warden keeps one machine-wide registry of who holds which simulator, emulator and port. Read this page to know why a claim waits, why a lease expired, or why warden refuses to touch a device.
warden ls # who holds what: resource, state, owner, repo/worktree, age, heartbeatLeases
A lease is an exclusive claim on one resource: a device (iOS sim or Android emulator, by udid / serial), a TCP port, or a build lock (one fingerprint being fetched or built; see App builds). Every lease has an id, an owner, an optional label, acquiredAt, heartbeatAt, a TTL and, where known, a pid.
All leases live in one sqlite database, ~/.warden/warden.db, in WAL mode. Every claim runs inside BEGIN IMMEDIATE, which is the cross-process mutex: two processes claiming at the same instant are serialised, and the second sees the first's lease.
Owners
warden works out who is asking:
| Owner | Detected from |
|---|---|
| agent | hook stdin session id, else WARDEN_SESSION_ID, CLAUDE_CODE_SESSION_ID or CLAUDE_SESSION_ID |
| ci | CI set to true / 1, or GITHUB_ACTIONS=true |
| user | the parent shell's pid |
Checked in that order, except that CI wins over the session env vars: a CI run is ci even with a session id exported.
The repo and worktree are recorded with the lease, so warden ls and a blocked warden ui call can tell you which worktree holds a device.
Liveness
A lease is alive while its pid is alive or its heartbeat is within the TTL (default 30 min; 5 min for a bare warden claim by an agent). Stale leases (pid dead and heartbeat expired) are reclaimed on every claim and by warden gc. Leases held by warden run / dev / batch / e2e record their heartbeat period: once that process is gone (killed, crashed), its leases go stale after three missed heartbeats (90 s) instead of the TTL.
A bare agent warden claim prints a hint and expires in 5 min; nothing refreshes it automatically. warden dev, run, batch and e2e heartbeat every 30 s and hold their devices; otherwise keep a lease alive with warden heartbeat <leaseId> (or --mine). warden gc shuts a stale lease's sim down only 10+ min after the lease went stale, and never while a non-Apple app is running on it.
Which devices warden touches
- warden creates and reuses its own
warden-<profile>-Nsimulators in the default CoreSimulator set, and launches Android emulators with-read-only. - A booted device with no lease that warden didn't create is foreign: your Simulator.app, another tool's sim. It is never allocated unless you pass
--adopt, and never shut down, erased or installed on. - The pool grows on demand up to
--maxdevices per profile (default: cores / 4, at least 1 and capped at 4, but never below--count). How many may be booted at once is set by host RAM and CPU: a claim that does not fit waits in a priority queue,--wait 30mby default. See Capacity & queue. warden gcshuts down idle warden devices after 20 min (--idlechanges it). It never deletes them. It also prunes the app-build cache to--max-cache-size(default 20G;$WARDEN_MAX_CACHE_SIZE; builds used in the last 30 min stay).claim/runstart it in the background at most every 10 min;WARDEN_AUTO_GC=0turns that off.
Shutdown policy
Only sims warden created, or that the lease owner booted itself (they were off when it first touched them), are shut down on release. A sim that was already running when it was picked up is released but left on.