Skip to content

fix(devices): resolve the Android SDK per spawn, not once at startup - #11647

Open
thelonewander3r wants to merge 1 commit into
pingdotgg:mainfrom
thelonewander3r:fix/device-android-sdk-stale-resolution
Open

thelonewander3r wants to merge 1 commit into
pingdotgg:mainfrom
thelonewander3r:fix/device-android-sdk-stale-resolution

Conversation

@thelonewander3r

@thelonewander3r thelonewander3r commented Sep 13, 2026 •

Copy link
Copy Markdown

What Changed

LocalDeviceHost.make resolved androidSdk once at construction and captured the result in run and in the environment handed to the device hub and the agent-device daemon. That resolution now happens per spawn, in a new module-level hostSpawn.

No behaviour changes when the SDK is present before the server starts.

Why

platformReason re-resolves the SDK on every availability check, while run held a copy from startup. The two drift apart as soon as the SDK is installed, moved, or repaired after the server started, and then disagree permanently:

if ((yield* host.platformAvailability("android")).available) { // fresh -> true
  const avds = yield* ready.run("emulator", ["-list-avds"]);   // captured -> bare "emulator"

With sdk.root still null, run spawns a bare emulator that is not on the server's PATH. ProcessRunner maps the spawn failure to exit 127, and fetchDevices turns any non-zero exit into a hard error — so the whole device list fails, iOS simulators included, and stays that way until the app restarts.

Observed on a machine with a complete, working SDK at ~/Library/Android/sdk:

"platforms":[{"platform":"ios","available":true},{"platform":"android","available":true}],
"hostStatuses":{"local":{"status":"failed","detail":"Device list failed: The device command failed (exit code 127)."}}

Android reports available and the same call reports failed. The underlying cause was ProcessSpawnError: Failed to spawn process 'emulator' — note the bare name, which run only passes when sdk.root is falsy.

The same stale value reaches the hub and the daemon, which were spawned with no ANDROID_HOME and without the SDK on their PATH, so they cannot see emulators either.

Resolving per spawn is what keeps spawns and availability reading the same SDK. The cost is one extra SDK probe per spawn, which is the same work platformAvailability already does two to three times per refresh.

Tests

Three cases in LocalDeviceHost.test.ts covering the absolute emulator path, passthrough for other commands, and an SDK that appears after an earlier spawn resolved nothing. Reintroducing the cache makes the third fail.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Android emulator launches now detect the current Android SDK location each time a device starts.
    • Device commands and environment settings are refreshed at launch, improving recovery when the SDK becomes available after an earlier lookup failure.
    • Non-emulator commands continue to run without modification.

`LocalDeviceHost.make` resolved `androidSdk` once and captured the result in
`run` and in the environment handed to the hub and the agent-device daemon.
`platformReason` re-resolves on every availability check, so the two drift
apart as soon as the SDK is installed, moved, or repaired after the server
started: availability reports Android as usable, while `run` still spawns a
bare `emulator` that is not on PATH.

`fetchDevices` calls `run("emulator", ["-list-avds"])` whenever Android reports
available and turns a non-zero exit into a hard error, so the whole device list
fails — iOS simulators included — with "Device list failed: The device command
failed (exit code 127)", and stays that way until the app restarts. The hub and
daemon are also left without ANDROID_HOME and without the SDK on their PATH.

Move the command and environment resolution into `hostSpawn`, evaluated per
spawn, so spawns and availability always read the same SDK.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Sep 13, 2026
@macroscopeapp

macroscopeapp Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at bc154c2

Macroscope's review found this PR approvable — This is a narrowly scoped device-host bug fix that resolves the Android SDK per command, preventing stale startup state from breaking emulator discovery after SDK changes. Existing startup behavior remains intact, and targeted tests cover absolute-path execution and dynamic SDK discovery.

You can add or adjust custom eligibility rules. Learn more.

@coderabbitai

coderabbitai Bot commented Sep 13, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: b3b6711e-7c1a-4fa0-bb95-85a8526744fb

📥 Commits

Reviewing files that changed from the base of the PR and between 77bca8b and bc154c2.

📒 Files selected for processing (2)
  • apps/server/src/device/LocalDeviceHost.test.ts
  • apps/server/src/device/LocalDeviceHost.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

LocalDeviceHost now resolves the Android SDK, executable command, and environment for each spawn. Emulator commands use the current SDK when available. Host, hub, daemon, and run paths consume the per-spawn results. Tests cover command mapping, environment setup, and SDK rediscovery.

Changes

LocalDeviceHost spawn resolution

Layer / File(s) Summary
Per-spawn command resolution
apps/server/src/device/LocalDeviceHost.ts, apps/server/src/device/LocalDeviceHost.test.ts
hostSpawn resolves the current SDK and environment for each call. Emulator commands map to the SDK emulator path when available. Tests cover emulator mapping, unchanged commands, environment setup, and SDK rediscovery.
Resolved environment propagation
apps/server/src/device/LocalDeviceHost.ts
Host, hub, and agent-device daemon paths now consume the per-spawn environment effect.
Command execution integration
apps/server/src/device/LocalDeviceHost.ts
run uses the resolved command and environment while preserving timeout, input, result, and exit-code handling.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant LocalDeviceHost
  participant androidSdk
  participant FileSystem
  participant ChildProcess
  LocalDeviceHost->>androidSdk: Resolve SDK for spawn
  androidSdk->>FileSystem: Check current SDK availability
  LocalDeviceHost->>LocalDeviceHost: Build command and environment
  LocalDeviceHost->>ChildProcess: Spawn resolved command with environment
Loading

Suggested reviewers: juliusmarminge

Merge Risk: ⚪ Minimal · up to bc154

The per-launch SDK resolution is propagated to the affected process paths, including SDK rediscovery, with no concrete merge-blocking issue identified.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: resolving the Android SDK for each process spawn instead of once at startup.
Description check ✅ Passed The description clearly explains what changed, why the change is needed, the failure mode, and the tests added. The UI Changes and Checklist sections are omitted, but no UI changes are described and t…
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 2…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant