Emulator serials are ports, and ports get recycled. The cache checked only
that its cached serial was still in `adb devices`, which stays true after
the AVD that was on it exits and a different one takes the port -- so the
entry never expires and every call resolves to somebody else's emulator.
Measured on 2026-09-04 with ai-app.serial and ai-app-2.serial both holding
emulator-5554: from ~/repos/ai-app-2, `emu up` reported "'ai-app-2' is
already running (emulator-5554)" and handed back a device that
`adb -s emulator-5554 emu avd name` calls ai-app. Every adb, ui-trace and
emu call from that checkout went to the other session's emulator, and the
right AVD could not be started at all because the tool believed it was up.
That is the exact fan-out this repo exists to prevent, and nothing says so.
Asking the cached serial its name is one round trip against one device, not
the per-device sweep the cache exists to avoid, and a mismatch drops the
entry so the slow path can answer. It goes in avd_serial because `adb`,
`ui-trace` and both of `emu`'s uses -- resolving a serial, and deciding
whether to start one -- already come through it; a check in only one of
them would be the same bug with a smaller blast radius.
Found from ai-app-2; ai-app's session confirmed its own work had landed on
the right device and asked for the fix here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ui-trace, the adb wrapper and its device recorder were loose files under
~/.local, and each Android checkout carried its own copy of the same
emulator boot sequence. This puts them together, with an install script that
symlinks them back so editing the repo is editing what runs.
Two things are new rather than moved. `emu` is the emulator lifecycle --
name, serial, list, up, down -- keyed on the AVD named after the enclosing
checkout, which is the rule that lets several sessions work here at once;
and `adb` now fills in `-s` from that same rule, because with two emulators
attached a bare `adb shell pm list packages` comes back empty rather than
failing, which reads as the app being uninstalled rather than the question
being ambiguous.
`emu up` refuses when the machine has no room. On 2026-08-30 an emulator
started with 2.8 GB available invoked the OOM killer, and what it took was
not the emulator that had just started: it walked the user slice and killed
pipewire, dbus-broker and another session's emulator first.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>