From dbc01611f7ed3f06cfcfe47d4cde5ada3aa3ae8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Sat, 3 Oct 2026 13:45:53 +0200 Subject: [PATCH 1/3] fix(android): fail doctor when adb is the Windows binary on a POSIX host Under WSL, Windows adb.exe passes adb version but resolves host paths as Windows paths, so recording/pull/install break. Read adb's Installed as line and fail the toolchain check with reason android_adb_windows_binary_on_posix_host plus a Linux platform-tools hint. --- .../src/__tests__/doctor.test.ts | 56 ++++++++++++++++++- packages/platform-android/src/doctor.ts | 46 +++++++++++++-- packages/provision-kit/src/toolchain-probe.ts | 9 ++- src/platform-runtime-host-diagnostics.ts | 2 +- website/docs/docs/known-limitations.md | 4 ++ 5 files changed, 109 insertions(+), 8 deletions(-) diff --git a/packages/platform-android/src/__tests__/doctor.test.ts b/packages/platform-android/src/__tests__/doctor.test.ts index 6a705a3c42..f2fd982fa8 100644 --- a/packages/platform-android/src/__tests__/doctor.test.ts +++ b/packages/platform-android/src/__tests__/doctor.test.ts @@ -1,4 +1,6 @@ import assert from 'node:assert/strict'; +import { promises as fs } from 'node:fs'; +import path from 'node:path'; import { afterEach, test, vi } from 'vitest'; const HELPER_SERVICE = 'com.callstack.agentdevice.imehelper/.TestInputMethodService'; @@ -29,7 +31,8 @@ vi.mock('../ime-helper.ts', async (importOriginal) => { import { ANDROID_EMULATOR } from './test-utils/device-fixtures.ts'; import type { HostDiagnosticsContext } from '@agent-device/contracts/host-diagnostics'; -import { androidDeviceChecks } from '../doctor.ts'; +import { androidDeviceChecks, androidToolchainCheck } from '../doctor.ts'; +import { mkdtempForTest } from './test-utils/tmp-dir.ts'; import { resetAndroidTestImeActivationCacheForTests, setAndroidTestImeActiveForTests, @@ -39,6 +42,7 @@ import type { DoctorCheck } from '@agent-device/contracts/observability'; afterEach(() => { resetAndroidTestImeActivationCacheForTests(); + vi.unstubAllEnvs(); }); function fakeAdb(currentIme: string, previousIme = 'null'): AndroidAdbExecutor { @@ -96,3 +100,53 @@ test('falls back to ime list -s when no previous-IME record was persisted', asyn assert.equal(check?.status, 'fail'); assert.equal(check?.command, `adb -s ${ANDROID_EMULATOR.id} shell ime list -s`); }); + +const WINDOWS_ADB_PATH = String.raw`C:\Users\dev\AppData\Local\Android\Sdk\platform-tools\adb.exe`; +const WINDOWS_ADB_VERSION = [ + 'Android Debug Bridge version 1.0.41', + 'Version 35.0.2-12147458', + `Installed as ${WINDOWS_ADB_PATH}`, + 'Running on Windows 10.0.26100', +].join('\n'); +const LINUX_ADB_VERSION = [ + 'Android Debug Bridge version 1.0.41', + 'Version 35.0.2-12147458', + 'Installed as /home/dev/Android/Sdk/platform-tools/adb', + 'Running on Linux 6.18.0-microsoft-standard-WSL2 (x86_64)', +].join('\n'); + +async function toolchainCheckWithAdbVersion( + versionOutput: string, + hostPlatform: NodeJS.Platform = 'linux', +): Promise { + const binDir = await mkdtempForTest('agent-device-android-doctor-'); + const adbPath = path.join(binDir, 'adb'); + await fs.writeFile(adbPath, `#!/bin/sh\ncat <<'EOF'\n${versionOutput}\nEOF\n`, 'utf8'); + await fs.chmod(adbPath, 0o755); + vi.stubEnv('PATH', `${binDir}${path.delimiter}${process.env.PATH ?? ''}`); + return await androidToolchainCheck( + { ANDROID_HOME: '/mnt/c/Users/dev/AppData/Local/Android/Sdk' }, + hostPlatform, + { access: async () => {} }, + ); +} + +test('fails the toolchain when adb on a POSIX host is the Windows binary', async () => { + const check = await toolchainCheckWithAdbVersion(WINDOWS_ADB_VERSION); + assert.equal(check.status, 'fail'); + assert.equal(check.evidence?.reason, 'android_adb_windows_binary_on_posix_host'); + assert.equal(check.evidence?.adbPath, WINDOWS_ADB_PATH); + assert.match(check.hint ?? '', /Linux Android platform-tools/); +}); + +test('passes the toolchain when adb is the Linux binary, even under WSL', async () => { + const check = await toolchainCheckWithAdbVersion(LINUX_ADB_VERSION); + assert.equal(check.status, 'pass'); + assert.equal(check.evidence?.reason, undefined); +}); + +test('passes the toolchain when the Windows binary runs on a Windows host', async () => { + const check = await toolchainCheckWithAdbVersion(WINDOWS_ADB_VERSION, 'win32'); + assert.equal(check.status, 'pass'); + assert.equal(check.evidence?.reason, undefined); +}); diff --git a/packages/platform-android/src/doctor.ts b/packages/platform-android/src/doctor.ts index d11a53d0fc..d45c891cea 100644 --- a/packages/platform-android/src/doctor.ts +++ b/packages/platform-android/src/doctor.ts @@ -3,7 +3,7 @@ import type { DeviceInfo } from '@agent-device/kernel/device'; import { normalizeError } from '@agent-device/kernel/errors'; import type { DoctorCheck } from '@agent-device/contracts/observability'; import type { HostDiagnosticsContext } from '@agent-device/contracts/host-diagnostics'; -import { commandFirstLine } from '@agent-device/provision-kit/toolchain-probe'; +import { commandOutput, firstOutputLine } from '@agent-device/provision-kit/toolchain-probe'; import { resolveAndroidAdbExecutor, runAdbShell, type AndroidAdbExecutor } from './adb-executor.ts'; import { isAndroidTestImeActive, @@ -18,6 +18,7 @@ import { } from './adb-host.ts'; const ANDROID_PROBE_TIMEOUT_MS = 2000; +const WINDOWS_ADB_ON_POSIX_HOST_REASON = 'android_adb_windows_binary_on_posix_host'; type AndroidLicenseState = 'accepted' | 'missing' | 'unknown'; type AndroidToolchainProbe = { @@ -27,13 +28,19 @@ type AndroidToolchainProbe = { }; export async function androidToolchainCheck( - environment: AndroidAdbEnvironment = requireAndroidAdbHost().environment, + environment: AndroidAdbEnvironment, + hostPlatform: NodeJS.Platform, files: Pick = requireAndroidAdbHost().files, ): Promise { const sdkRoot = environment.ANDROID_HOME || environment.ANDROID_SDK_ROOT; const license = await androidLicenseState(sdkRoot, files); - const versionLine = await commandFirstLine('adb', ['version']); - if (!versionLine) return missingAndroidAdbCheck(sdkRoot, license); + const versionOutput = await commandOutput('adb', ['version']); + const versionLine = versionOutput === undefined ? undefined : firstOutputLine(versionOutput); + if (!versionOutput || !versionLine) return missingAndroidAdbCheck(sdkRoot, license); + const adbPath = androidAdbInstallPath(versionOutput); + if (hostPlatform !== 'win32' && adbPath && isWindowsHostPath(adbPath)) { + return windowsAdbOnPosixHostCheck({ adbPath, sdkRoot, versionLine }); + } return androidAdbCheck({ license, @@ -173,6 +180,37 @@ async function probeAndroidReverse( } } +/** The binary path adb reports for itself on its `Installed as` version line. */ +function androidAdbInstallPath(versionOutput: string): string | undefined { + return /^Installed as (.+)$/m.exec(versionOutput)?.[1]?.trim(); +} + +/** A drive-letter or UNC path, which only a Windows binary reports. */ +function isWindowsHostPath(candidate: string): boolean { + return /^(?:[A-Za-z]:[\\/]|\\\\)/.test(candidate); +} + +/** + * A Windows adb.exe reached from a Linux host (WSL interop) answers `adb version`, but it resolves + * every host path it is handed as a Windows path, so pulls, pushes, and installs miss the Linux files. + */ +function windowsAdbOnPosixHostCheck( + probe: Readonly<{ adbPath: string; sdkRoot: string | undefined; versionLine: string }>, +): DoctorCheck { + return { + id: 'toolchain', + status: 'fail', + summary: `Android toolchain: adb on PATH is the Windows binary ${probe.adbPath}, which cannot use this host's file paths.`, + hint: 'Under WSL, install Linux Android platform-tools, put them first on PATH, and point ANDROID_HOME at a Linux SDK instead of one under /mnt/.', + evidence: { + reason: WINDOWS_ADB_ON_POSIX_HOST_REASON, + adbPath: probe.adbPath, + adbVersion: probe.versionLine, + androidHome: probe.sdkRoot ?? null, + }, + }; +} + function androidAdbCheck(probe: AndroidToolchainProbe): DoctorCheck { return { id: 'toolchain', diff --git a/packages/provision-kit/src/toolchain-probe.ts b/packages/provision-kit/src/toolchain-probe.ts index 8359d7173a..45cdfdb78c 100644 --- a/packages/provision-kit/src/toolchain-probe.ts +++ b/packages/provision-kit/src/toolchain-probe.ts @@ -3,10 +3,15 @@ import { runCmd } from '@agent-device/host-kit/command'; export const TOOLCHAIN_TIMEOUT_MS = 3_000; export async function commandFirstLine(cmd: string, args: string[]): Promise { + const output = await commandOutput(cmd, args); + return output === undefined ? undefined : firstOutputLine(output); +} + +/** Stdout of a toolchain probe that exited 0, or undefined when the tool is missing or failed. */ +export async function commandOutput(cmd: string, args: string[]): Promise { try { const result = await runCmd(cmd, args, { allowFailure: true, timeoutMs: TOOLCHAIN_TIMEOUT_MS }); - if (result.exitCode !== 0) return undefined; - return firstOutputLine(result.stdout); + return result.exitCode === 0 ? result.stdout : undefined; } catch { return undefined; } diff --git a/src/platform-runtime-host-diagnostics.ts b/src/platform-runtime-host-diagnostics.ts index 2e5349dd7b..35893c5874 100644 --- a/src/platform-runtime-host-diagnostics.ts +++ b/src/platform-runtime-host-diagnostics.ts @@ -19,7 +19,7 @@ export function createHostDiagnostics(): HostDiagnostics { ): Promise => { if (platform === 'android') { const { androidToolchainCheck } = await loadAndroidMechanics(); - return await androidToolchainCheck(process.env); + return await androidToolchainCheck(process.env, process.platform); } if (platform === 'vega') { const { vegaToolchainCheck } = await import('@agent-device/platform-vega'); diff --git a/website/docs/docs/known-limitations.md b/website/docs/docs/known-limitations.md index 5b39525abb..565450dc74 100644 --- a/website/docs/docs/known-limitations.md +++ b/website/docs/docs/known-limitations.md @@ -44,6 +44,10 @@ Some OEM builds gate the first install of a package behind the system package in An unattended first Android snapshot therefore times out with a helper install failure whose hint says to check the device screen for a pending install confirmation. Confirm the prompts on the device and retry; if no dialog is showing, restart the ADB server as the hint says. +## Android: WSL needs Linux platform-tools + +Under WSL, the Windows `adb.exe` (for example from an `ANDROID_HOME` under `/mnt/c`) answers `adb version` but resolves every host path it is given as a Windows path, so recordings, pulls, and installs fail. Install Linux Android platform-tools inside WSL, put them first on `PATH`, and point `ANDROID_HOME` at a Linux SDK. `agent-device doctor` fails the toolchain check with reason `android_adb_windows_binary_on_posix_host` when `adb` reports a Windows install path. + ## Android: no clipboard access over adb on Android 16 `agent-device` reaches the Android clipboard through `adb shell cmd clipboard`. That command works only on a build whose clipboard service implements a shell command, and AOSP's `ClipboardService` does not: the class carries no shell command at `android13-release`, `android14-release`, `android15-release` or `android16-release`, nor on current AOSP `main`, and a physical device runs that same class. On Android 16 (API 36) every `cmd clipboard get text` and `cmd clipboard set text ` call is answered by the framework's default `Binder.handleShellCommand` — `No shell command implementation.` on stderr, exit status **0** — so the clipboard is never touched even though the call reports success. From 90193efb7087a65940ffb94d0a66b5093811ad91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Sat, 3 Oct 2026 16:27:40 +0200 Subject: [PATCH 2/3] fix(android): name the adb host invariant and catch Windows adb without Installed as The hint fired on every non-win32 host but only advised WSL users; state the invariant that adb must be a native binary for this host and keep WSL as the example. Treat a "Running on Windows" banner as the same signal so builds that omit "Installed as" fail closed instead of passing green, and key the check on detectedVia. --- .../src/__tests__/doctor.test.ts | 45 ++++++++++++++++- packages/platform-android/src/doctor.ts | 48 +++++++++++++++---- website/docs/docs/known-limitations.md | 2 +- 3 files changed, 84 insertions(+), 11 deletions(-) diff --git a/packages/platform-android/src/__tests__/doctor.test.ts b/packages/platform-android/src/__tests__/doctor.test.ts index f2fd982fa8..32a372f7ed 100644 --- a/packages/platform-android/src/__tests__/doctor.test.ts +++ b/packages/platform-android/src/__tests__/doctor.test.ts @@ -114,6 +114,16 @@ const LINUX_ADB_VERSION = [ 'Installed as /home/dev/Android/Sdk/platform-tools/adb', 'Running on Linux 6.18.0-microsoft-standard-WSL2 (x86_64)', ].join('\n'); +// adb builds before 1.0.36 and some third-party builds omit the `Installed as` banner, leaving the +// `Running on Windows` line as the only signal that the binary is a Windows one. +const WINDOWS_ADB_VERSION_WITHOUT_INSTALL_PATH = [ + 'Android Debug Bridge version 1.0.39', + 'Running on Windows 10.0.26100', +].join('\n'); +const LINUX_ADB_VERSION_WITHOUT_INSTALL_PATH = [ + 'Android Debug Bridge version 1.0.39', + 'Running on Linux 6.18.0 (x86_64)', +].join('\n'); async function toolchainCheckWithAdbVersion( versionOutput: string, @@ -135,8 +145,32 @@ test('fails the toolchain when adb on a POSIX host is the Windows binary', async const check = await toolchainCheckWithAdbVersion(WINDOWS_ADB_VERSION); assert.equal(check.status, 'fail'); assert.equal(check.evidence?.reason, 'android_adb_windows_binary_on_posix_host'); + assert.equal(check.evidence?.detectedVia, 'installed-as-path'); assert.equal(check.evidence?.adbPath, WINDOWS_ADB_PATH); - assert.match(check.hint ?? '', /Linux Android platform-tools/); + assert.match(check.summary ?? '', /Windows binary/); + assert.match(check.hint ?? '', /native binary for this host/); + assert.match(check.hint ?? '', /Under WSL/); +}); + +test('names the invariant in the hint for a non-WSL host reaching a Windows adb', async () => { + const check = await toolchainCheckWithAdbVersion(WINDOWS_ADB_VERSION, 'darwin'); + assert.equal(check.status, 'fail'); + assert.equal(check.evidence?.detectedVia, 'installed-as-path'); + assert.match(check.hint ?? '', /^adb must be a native binary for this host/); +}); + +test('fails the toolchain from the Running on Windows line when adb omits Installed as', async () => { + const check = await toolchainCheckWithAdbVersion(WINDOWS_ADB_VERSION_WITHOUT_INSTALL_PATH); + assert.equal(check.status, 'fail'); + assert.equal(check.evidence?.reason, 'android_adb_windows_binary_on_posix_host'); + assert.equal(check.evidence?.detectedVia, 'running-on-line'); + assert.equal(check.evidence?.adbPath, null); +}); + +test('keeps a native binary passing when adb omits Installed as', async () => { + const check = await toolchainCheckWithAdbVersion(LINUX_ADB_VERSION_WITHOUT_INSTALL_PATH); + assert.equal(check.status, 'pass'); + assert.equal(check.evidence?.reason, undefined); }); test('passes the toolchain when adb is the Linux binary, even under WSL', async () => { @@ -150,3 +184,12 @@ test('passes the toolchain when the Windows binary runs on a Windows host', asyn assert.equal(check.status, 'pass'); assert.equal(check.evidence?.reason, undefined); }); + +test('passes the toolchain on a Windows host when adb only reports Running on Windows', async () => { + const check = await toolchainCheckWithAdbVersion( + WINDOWS_ADB_VERSION_WITHOUT_INSTALL_PATH, + 'win32', + ); + assert.equal(check.status, 'pass'); + assert.equal(check.evidence?.reason, undefined); +}); diff --git a/packages/platform-android/src/doctor.ts b/packages/platform-android/src/doctor.ts index d45c891cea..86b86b16fa 100644 --- a/packages/platform-android/src/doctor.ts +++ b/packages/platform-android/src/doctor.ts @@ -37,9 +37,9 @@ export async function androidToolchainCheck( const versionOutput = await commandOutput('adb', ['version']); const versionLine = versionOutput === undefined ? undefined : firstOutputLine(versionOutput); if (!versionOutput || !versionLine) return missingAndroidAdbCheck(sdkRoot, license); - const adbPath = androidAdbInstallPath(versionOutput); - if (hostPlatform !== 'win32' && adbPath && isWindowsHostPath(adbPath)) { - return windowsAdbOnPosixHostCheck({ adbPath, sdkRoot, versionLine }); + const windowsAdb = hostPlatform === 'win32' ? undefined : detectWindowsAdb(versionOutput); + if (windowsAdb) { + return windowsAdbOnPosixHostCheck({ windowsAdb, sdkRoot, versionLine }); } return androidAdbCheck({ @@ -190,21 +190,51 @@ function isWindowsHostPath(candidate: string): boolean { return /^(?:[A-Za-z]:[\\/]|\\\\)/.test(candidate); } +/** Banner line naming the OS the running adb was built for; a native host binary never names Windows. */ +function reportsWindowsRuntime(versionOutput: string): boolean { + return /^Running on Windows\b/m.test(versionOutput); +} + +/** adb's self-report that it is a Windows binary, plus the signal that revealed it. */ +type WindowsAdbReport = Readonly<{ + detectedVia: 'installed-as-path' | 'running-on-line'; + adbPath: string | undefined; +}>; + +function detectWindowsAdb(versionOutput: string): WindowsAdbReport | undefined { + const installPath = androidAdbInstallPath(versionOutput); + if (installPath && isWindowsHostPath(installPath)) { + return { detectedVia: 'installed-as-path', adbPath: installPath }; + } + if (reportsWindowsRuntime(versionOutput)) { + return { detectedVia: 'running-on-line', adbPath: undefined }; + } + return undefined; +} + /** - * A Windows adb.exe reached from a Linux host (WSL interop) answers `adb version`, but it resolves - * every host path it is handed as a Windows path, so pulls, pushes, and installs miss the Linux files. + * A Windows adb.exe reached from a POSIX host (WSL interop, Wine, a mounted volume) answers + * `adb version`, but it resolves every host path it is handed as a Windows path, so pulls, pushes, + * and installs miss the host's files. Older and third-party adb builds omit the `Installed as` + * banner, so the `Running on Windows` line is the fallback signal for them. */ function windowsAdbOnPosixHostCheck( - probe: Readonly<{ adbPath: string; sdkRoot: string | undefined; versionLine: string }>, + probe: Readonly<{ + windowsAdb: WindowsAdbReport; + sdkRoot: string | undefined; + versionLine: string; + }>, ): DoctorCheck { + const { adbPath, detectedVia } = probe.windowsAdb; return { id: 'toolchain', status: 'fail', - summary: `Android toolchain: adb on PATH is the Windows binary ${probe.adbPath}, which cannot use this host's file paths.`, - hint: 'Under WSL, install Linux Android platform-tools, put them first on PATH, and point ANDROID_HOME at a Linux SDK instead of one under /mnt/.', + summary: `Android toolchain: adb on PATH${adbPath ? ` (${adbPath})` : ''} is a Windows binary, which cannot use this host's file paths.`, + hint: "adb must be a native binary for this host: install this host's Android platform-tools, put them first on PATH, and point ANDROID_HOME at an SDK on this host's filesystem. Under WSL that means a Linux SDK, not one under /mnt/.", evidence: { reason: WINDOWS_ADB_ON_POSIX_HOST_REASON, - adbPath: probe.adbPath, + detectedVia, + adbPath: adbPath ?? null, adbVersion: probe.versionLine, androidHome: probe.sdkRoot ?? null, }, diff --git a/website/docs/docs/known-limitations.md b/website/docs/docs/known-limitations.md index 565450dc74..186a13c5bd 100644 --- a/website/docs/docs/known-limitations.md +++ b/website/docs/docs/known-limitations.md @@ -46,7 +46,7 @@ An unattended first Android snapshot therefore times out with a helper install f ## Android: WSL needs Linux platform-tools -Under WSL, the Windows `adb.exe` (for example from an `ANDROID_HOME` under `/mnt/c`) answers `adb version` but resolves every host path it is given as a Windows path, so recordings, pulls, and installs fail. Install Linux Android platform-tools inside WSL, put them first on `PATH`, and point `ANDROID_HOME` at a Linux SDK. `agent-device doctor` fails the toolchain check with reason `android_adb_windows_binary_on_posix_host` when `adb` reports a Windows install path. +Under WSL, the Windows `adb.exe` (for example from an `ANDROID_HOME` under `/mnt/c`) answers `adb version` but resolves every host path it is given as a Windows path, so recordings, pulls, and installs fail. Install Linux Android platform-tools inside WSL, put them first on `PATH`, and point `ANDROID_HOME` at a Linux SDK. `agent-device doctor` fails the toolchain check with reason `android_adb_windows_binary_on_posix_host` when `adb` reports a Windows install path or reports that it is running on Windows. The same check catches a Windows `adb.exe` reached from macOS or Linux through Wine or a mounted volume. ## Android: no clipboard access over adb on Android 16 From d40fd854e94f279370e9b9c28a5a96d1771ca44c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Sat, 3 Oct 2026 17:14:02 +0200 Subject: [PATCH 3/3] docs(android): name WSL interop or Wine as the adb.exe execution path --- packages/platform-android/src/doctor.ts | 2 +- website/docs/docs/known-limitations.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/platform-android/src/doctor.ts b/packages/platform-android/src/doctor.ts index 86b86b16fa..ad8ff26b49 100644 --- a/packages/platform-android/src/doctor.ts +++ b/packages/platform-android/src/doctor.ts @@ -213,7 +213,7 @@ function detectWindowsAdb(versionOutput: string): WindowsAdbReport | undefined { } /** - * A Windows adb.exe reached from a POSIX host (WSL interop, Wine, a mounted volume) answers + * A Windows adb.exe reached from a POSIX host through WSL interop or Wine answers * `adb version`, but it resolves every host path it is handed as a Windows path, so pulls, pushes, * and installs miss the host's files. Older and third-party adb builds omit the `Installed as` * banner, so the `Running on Windows` line is the fallback signal for them. diff --git a/website/docs/docs/known-limitations.md b/website/docs/docs/known-limitations.md index 186a13c5bd..81ada8f29a 100644 --- a/website/docs/docs/known-limitations.md +++ b/website/docs/docs/known-limitations.md @@ -46,7 +46,7 @@ An unattended first Android snapshot therefore times out with a helper install f ## Android: WSL needs Linux platform-tools -Under WSL, the Windows `adb.exe` (for example from an `ANDROID_HOME` under `/mnt/c`) answers `adb version` but resolves every host path it is given as a Windows path, so recordings, pulls, and installs fail. Install Linux Android platform-tools inside WSL, put them first on `PATH`, and point `ANDROID_HOME` at a Linux SDK. `agent-device doctor` fails the toolchain check with reason `android_adb_windows_binary_on_posix_host` when `adb` reports a Windows install path or reports that it is running on Windows. The same check catches a Windows `adb.exe` reached from macOS or Linux through Wine or a mounted volume. +Under WSL, the Windows `adb.exe` (for example from an `ANDROID_HOME` under `/mnt/c`) answers `adb version` but resolves every host path it is given as a Windows path, so recordings, pulls, and installs fail. Install Linux Android platform-tools inside WSL, put them first on `PATH`, and point `ANDROID_HOME` at a Linux SDK. `agent-device doctor` fails the toolchain check with reason `android_adb_windows_binary_on_posix_host` when `adb` reports a Windows install path or reports that it is running on Windows. The same check catches a Windows `adb.exe` reached from macOS or Linux through WSL interop or Wine. ## Android: no clipboard access over adb on Android 16