Skip to content

Fix avdmanager and sdkmanager on Windows - #110

Open
SunkenInTime wants to merge 1 commit into
expo:mainfrom
SunkenInTime:fix/windows-android-sdk-paths
Open

SunkenInTime wants to merge 1 commit into
expo:mainfrom
SunkenInTime:fix/windows-android-sdk-paths

Conversation

@SunkenInTime

Copy link
Copy Markdown

Problem

On Windows the Android cmdline-tools ship as avdmanager.bat and sdkmanager.bat. sdk-paths.ts builds the extension-less path, so every call fails and the Hub lists no emulators:

[android-utils] Failed to run `avdmanager list avd`: ENOENT

Appending .bat alone is not enough. Node refuses to spawn a batch file without a shell (spawn EINVAL, since 18.20 / 20.12).

Fix

  • sdk-paths.ts: append .bat to avdmanager and sdkmanager on win32. A trailing platform parameter (defaults to process.platform) keeps the helpers testable from any OS.
  • New exec-sdk-tool.ts: a .bat path runs through cmd.exe as one quoted command line; anything else still goes through execFile exactly as before. timeout and signal pass through in both cases.
  • adb and emulator are left alone. Windows resolves the missing .exe on its own (checked on this machine).

Proof

Windows 11, Node 24.13, Android SDK with cmdline-tools/latest. listDevices() bundled from main and from this branch, run under Node:

== main ==
error: [android-utils] Failed to run `avdmanager list avd`: ENOENT
== this branch ==
devices: [{"name":"T3Code_Pixel_8_API_36","booted":true,"serial":"emulator-5554"}]

The Hub CLI built from this branch, GET /api/devices:

{"simulators":[],"emulators":[{"id":"emulator-5554","name":"T3Code_Pixel_8_API_36","version":"Android 16.0","platform":"android","booted":true,"physical":false,"supported":true}],"errors":[]}

GET /api/new-device-options, which goes through sdkmanager --list_installed and avdmanager list device, returns the installed system image and device profiles.

Note: Bun's child_process resolves .bat files itself, so this never shows up under bun test or bun run. The published CLI runs under Node, where it does.

Tests

  • exec-sdk-tool.test.ts: quoting rules, direct spawn of native binaries, and a Windows-only case that runs a real .bat and checks that ; in a system image package and spaces in a path survive intact.
  • sdk-paths.test.ts: .bat on win32, bare name elsewhere.
  • bun test, tsc --noEmit, and oxlint pass in hub-android-utils. The pre-existing sdk-paths assertions compare POSIX strings, so they already fail on a Windows checkout; left as is.

🤖 Generated with Claude Code

On Windows the cmdline-tools ship as .bat wrappers. The path helpers
pointed at the extension-less name, which Node cannot find, and even
with the extension Node refuses to spawn a batch file without a shell.
Append .bat on win32 and run those two tools through cmd.exe as a
single quoted command line. adb and emulator are native binaries and
already resolve to their .exe.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@PastaGringo

Copy link
Copy Markdown

Independent confirmation on a second Windows machine — including the part that is
easy to get wrong.

I hit the same failure and had written a local patch before finding this PR. My
patch also appended .exe to emulator and adb. That turns out to be
unnecessary, which is worth stating explicitly because the reasoning is
counter-intuitive: existsSync returns false for the extension-less path
while execFile on that same path succeeds. The .exe resolution happens in
CreateProcess, not in the filesystem layer — so a quick existsSync check
would wrongly suggest the extension is needed.

Measured with promisify(execFile) under Node 24.19.0, Windows 11, Android SDK
at %LOCALAPPDATA%\Android\Sdk (a different SDK and different AVD set from the
ones in the PR description):

path existsSync execFile
emulator/emulator false okAndroid emulator version 37.1.11.0
emulator/emulator.exe true ok
platform-tools/adb false okAndroid Debug Bridge version 1.0.41
platform-tools/adb.exe true ok
cmdline-tools/latest/bin/avdmanager false ENOENT
cmdline-tools/latest/bin/avdmanager.bat true EINVAL

So both halves of the diagnosis reproduce independently: the missing .bat
(ENOENT) and Node's refusal to spawn a batch file without an interpreter
(EINVAL). And leaving adb/emulator untouched is correct, not an oversight.

One note on the control flow, for whoever reviews this: routing only .bat
paths through a shell — rather than switching the whole helper to shell: true
on Windows — also keeps the other SDK tools off the deprecated shell path. My
first patch used the broad version and it works, but it is a larger blast radius
than the defect requires.

Before the measurement above I ran the same probe with a wrong SDK path by
mistake. Every case failed, including the ones that should pass — which is the
only reason I noticed. Worth keeping the with-extension rows as a control if
anyone re-runs this: without them, "extension-less fails" and "the probe is
pointed at nothing" look identical.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants