On a single local machine, Gradle Managed Devices (GMD) can launch multiple
emulators concurrently: the e2e device group in app/build.gradle.kts spans
API levels 29-36, and gradle.properties already has org.gradle.parallel=true.
Running the whole group (./gradlew e2eGroupDebugAndroidTest) or even the /preflight skill's api36DebugAndroidTest alongside another in-flight GMD run
can therefore try to boot several emulators at once. They contend for the same
hardware virtualization slot (VT-x/HAXM) and hang at 0% CPU with errors like
"another emulator instance is running". Emulator tests need to run serially
(one emulator at a time) on a local machine.
This is AGP's Gradle Managed Device "max concurrent devices" option. I verified
the exact key (rather than trusting memory) by extracting com/android/build/gradle/options/IntegerOption.class from the com.android.tools.build:gradle:9.2.1 jar that this repo's root build.gradle.kts buildscript classpath pins — the enum constant GRADLE_MANAGED_DEVICE_MAX_CONCURRENT_DEVICES maps to the property string android.experimental.testOptions.managedDevices.maxConcurrentDevices, typed as
an IntegerOption under ApiStage.Experimental. A short comment explains why
(VT-x contention on single-machine local runs; keeps GMD emulator runs serial).
CI safety
This does not reduce CI's parallelism, for two independent reasons:
Each CI runner already runs exactly one emulator. The e2e matrix in .github/workflows/ci.yml (api-level: [29, 30, 31, 32, 33, 34, 35, 36])
fans out across separate GitHub Actions runners, one API level per runner —
there's no cross-level concurrency on a single machine to cap.
CI doesn't invoke Gradle Managed Device tasks at all. The e2e and e2e-preview jobs boot their emulator via reactivecircus/android-emulator-runner (or manual avdmanager provisioning
for the API 37 preview job) and then run ./gradlew connectedDebugAndroidTest
against that externally-provisioned emulator — never apXXDebugAndroidTest
or e2eGroupDebugAndroidTest. managedDevices.maxConcurrentDevices only
governs Gradle's own orchestration of testOptions.managedDevices-declared
devices, so it has no effect on CI's E2E jobs regardless of runner topology.
Validation
No emulator run and no full build, per instructions (an emulator run may be in
progress elsewhere). Validated with a light Gradle invocation only, using JDK 21
(JAVA_HOME pointed at JDK 25 by default in this environment, which AGP 9.2
doesn't support):
$env:JAVA_HOME = "C:\Program Files\Eclipse Adoptium\jdk-21.0.11.10-hotspot"
.\gradlew :app:help --warning-mode all
Result: BUILD SUCCESSFUL, with:
WARNING: The option setting 'android.experimental.testOptions.managedDevices.maxConcurrentDevices=1' is experimental.
That's AGP's standard acknowledgment for a known experimental option (naming
it back verbatim) — not an "unknown property" warning — confirming the key
parses and is recognized. No unrelated regressions: the only other warning in
the output is a pre-existing, unrelated Gradle 10 dependency-notation
deprecation notice already present on main.
## Problem
On a single local machine, Gradle Managed Devices (GMD) can launch multiple
emulators concurrently: the `e2e` device group in `app/build.gradle.kts` spans
API levels 29-36, and `gradle.properties` already has `org.gradle.parallel=true`.
Running the whole group (`./gradlew e2eGroupDebugAndroidTest`) or even the
`/preflight` skill's `api36DebugAndroidTest` alongside another in-flight GMD run
can therefore try to boot several emulators at once. They contend for the same
hardware virtualization slot (VT-x/HAXM) and hang at 0% CPU with errors like
"another emulator instance is running". Emulator tests need to run **serially**
(one emulator at a time) on a local machine.
## Fix
Added to `gradle.properties`:
```properties
android.experimental.testOptions.managedDevices.maxConcurrentDevices=1
```
This is AGP's Gradle Managed Device "max concurrent devices" option. I verified
the exact key (rather than trusting memory) by extracting
`com/android/build/gradle/options/IntegerOption.class` from the
`com.android.tools.build:gradle:9.2.1` jar that this repo's root
`build.gradle.kts` buildscript classpath pins — the enum constant
`GRADLE_MANAGED_DEVICE_MAX_CONCURRENT_DEVICES` maps to the property string
`android.experimental.testOptions.managedDevices.maxConcurrentDevices`, typed as
an `IntegerOption` under `ApiStage.Experimental`. A short comment explains why
(VT-x contention on single-machine local runs; keeps GMD emulator runs serial).
## CI safety
This does **not** reduce CI's parallelism, for two independent reasons:
1. **Each CI runner already runs exactly one emulator.** The `e2e` matrix in
`.github/workflows/ci.yml` (`api-level: [29, 30, 31, 32, 33, 34, 35, 36]`)
fans out across separate GitHub Actions runners, one API level per runner —
there's no cross-level concurrency on a single machine to cap.
2. **CI doesn't invoke Gradle Managed Device tasks at all.** The `e2e` and
`e2e-preview` jobs boot their emulator via
`reactivecircus/android-emulator-runner` (or manual `avdmanager` provisioning
for the API 37 preview job) and then run `./gradlew connectedDebugAndroidTest`
against that externally-provisioned emulator — never `apXXDebugAndroidTest`
or `e2eGroupDebugAndroidTest`. `managedDevices.maxConcurrentDevices` only
governs Gradle's own orchestration of `testOptions.managedDevices`-declared
devices, so it has no effect on CI's E2E jobs regardless of runner topology.
## Validation
No emulator run and no full build, per instructions (an emulator run may be in
progress elsewhere). Validated with a light Gradle invocation only, using JDK 21
(`JAVA_HOME` pointed at JDK 25 by default in this environment, which AGP 9.2
doesn't support):
```
$env:JAVA_HOME = "C:\Program Files\Eclipse Adoptium\jdk-21.0.11.10-hotspot"
.\gradlew :app:help --warning-mode all
```
Result: `BUILD SUCCESSFUL`, with:
```
WARNING: The option setting 'android.experimental.testOptions.managedDevices.maxConcurrentDevices=1' is experimental.
```
That's AGP's standard acknowledgment for a *known* experimental option (naming
it back verbatim) — not an "unknown property" warning — confirming the key
parses and is recognized. No unrelated regressions: the only other warning in
the output is a pre-existing, unrelated Gradle 10 dependency-notation
deprecation notice already present on `main`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
On a single local machine, Gradle Managed Devices (GMD) can launch multiple
emulators concurrently: the
e2edevice group inapp/build.gradle.ktsspansAPI levels 29-36, and
gradle.propertiesalready hasorg.gradle.parallel=true.Running the whole group (
./gradlew e2eGroupDebugAndroidTest) or even the/preflightskill'sapi36DebugAndroidTestalongside another in-flight GMD runcan therefore try to boot several emulators at once. They contend for the same
hardware virtualization slot (VT-x/HAXM) and hang at 0% CPU with errors like
"another emulator instance is running". Emulator tests need to run serially
(one emulator at a time) on a local machine.
Fix
Added to
gradle.properties:This is AGP's Gradle Managed Device "max concurrent devices" option. I verified
the exact key (rather than trusting memory) by extracting
com/android/build/gradle/options/IntegerOption.classfrom thecom.android.tools.build:gradle:9.2.1jar that this repo's rootbuild.gradle.ktsbuildscript classpath pins — the enum constantGRADLE_MANAGED_DEVICE_MAX_CONCURRENT_DEVICESmaps to the property stringandroid.experimental.testOptions.managedDevices.maxConcurrentDevices, typed asan
IntegerOptionunderApiStage.Experimental. A short comment explains why(VT-x contention on single-machine local runs; keeps GMD emulator runs serial).
CI safety
This does not reduce CI's parallelism, for two independent reasons:
e2ematrix in.github/workflows/ci.yml(api-level: [29, 30, 31, 32, 33, 34, 35, 36])fans out across separate GitHub Actions runners, one API level per runner —
there's no cross-level concurrency on a single machine to cap.
e2eande2e-previewjobs boot their emulator viareactivecircus/android-emulator-runner(or manualavdmanagerprovisioningfor the API 37 preview job) and then run
./gradlew connectedDebugAndroidTestagainst that externally-provisioned emulator — never
apXXDebugAndroidTestor
e2eGroupDebugAndroidTest.managedDevices.maxConcurrentDevicesonlygoverns Gradle's own orchestration of
testOptions.managedDevices-declareddevices, so it has no effect on CI's E2E jobs regardless of runner topology.
Validation
No emulator run and no full build, per instructions (an emulator run may be in
progress elsewhere). Validated with a light Gradle invocation only, using JDK 21
(
JAVA_HOMEpointed at JDK 25 by default in this environment, which AGP 9.2doesn't support):
Result:
BUILD SUCCESSFUL, with:That's AGP's standard acknowledgment for a known experimental option (naming
it back verbatim) — not an "unknown property" warning — confirming the key
parses and is recognized. No unrelated regressions: the only other warning in
the output is a pre-existing, unrelated Gradle 10 dependency-notation
deprecation notice already present on
main.🤖 Generated with Claude Code