You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug]: A thread refused with DEVICE_IN_USE can still tap on the device #31303
I searched existing issues and did not find a duplicate.
I included enough detail to reproduce or investigate the problem.
Area
apps/server
Steps to reproduce
Thread A: device_open an Android emulator, then <launcher> open com.android.settings.
Thread B: device_open the same emulator, then <launcher> open com.android.settings. This fails with DEVICE_IN_USE.
Thread B: <launcher> snapshot -i, then <launcher> press <x> <y>.
Expected behavior
Thread B's commands are refused while thread A holds the device.
Actual behavior
Only open is refused. B's snapshot returns A's screen, and B's press taps A's app:
$ B open com.android.settings -> Error (DEVICE_IN_USE) exit 1
$ B snapshot -i -> A's Settings screen exit 0
$ A session list -> A's session and B's, same emulator
$ B press 400 860 -> Tapped (400, 860) exit 0
This happened for real with several threads sharing one emulator: a thread refused at open went on to tap and swipe through another thread's app.
Cause
T3's device_open launcher adds --platform and --serial to every command.
agent-device 0.21.31 runs a command for a session that never opened if it has those flags. A snapshot run that way even creates the missing session. Only open and a few commands like install and boot check who holds the device.
This is different from #17470. There, a device stays held after a thread is done. Here, a held device doesn't stop another thread.
Reproduced in the wild on stable 0.1.7 (agent-device 0.21.31), Linux, local Android emulator (API 36). Two Claude Code threads were involved. Thread A's claim had been stale for hours (details on #17470).
From thread B's agent-device events.ndjson (UTC, Nov 6):
02:32:23.621 open error DEVICE_IN_USE (in use by session t3-e6e32ac5…, thread A)
02:32:23.880 snapshot ok (interactiveOnly)
02:32:30.060 press ok Tapped @e15 (515, 1514)
02:32:44.239 snapshot ok
Afterwards, session list showed both sessions on emulator-5554.
The agent ran these steps in one shell line and sent open's output to >/dev/null 2>&1. It never saw the refusal and went on to drive the emulator. The surrounding agent behavior was the bad part here, but it shows why only refusing open isn't enough: if one command's error is dropped, the session can still drive a device another thread holds. Refusing every command for a session that doesn't hold the claim, as #31304 proposes, would have stopped it.
Posted by Claude Code (Claude Opus 5.5) on behalf of @vinicius741.
Activity
vinicius741 commentedon Nov 6, 2026
Reproduced in the wild on stable 0.1.7 (agent-device 0.21.31), Linux, local Android emulator (API 36). Two Claude Code threads were involved. Thread A's claim had been stale for hours (details on #17470).
From thread B's agent-device
events.ndjson(UTC, Nov 6):Afterwards,
session listshowed both sessions onemulator-5554.The agent ran these steps in one shell line and sent
open's output to>/dev/null 2>&1. It never saw the refusal and went on to drive the emulator. The surrounding agent behavior was the bad part here, but it shows why only refusingopenisn't enough: if one command's error is dropped, the session can still drive a device another thread holds. Refusing every command for a session that doesn't hold the claim, as #31304 proposes, would have stopped it.Posted by Claude Code (Claude Opus 5.5) on behalf of @vinicius741.