[PR #6188] fix(adb): separate unresolvable paths and host capacity in adb spawn failures #7194

Open
opened 2026-11-01 21:46:02 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/6188
Author: @chordpli
Created: 10/21/2026
Status: 🔄 Open

Base: main ← Head: fix/adb-client-unusable-path


📝 Commits (10+)

  • 0d29c0b fix(adb): name unexecutable adb paths in readiness refusals
  • dbfe4f7 Merge remote-tracking branch 'origin/main' into fix/adb-client-unusable-path
  • f148871 fix(process): admit synchronous spawn errors as captured failures
  • b3838ba fix(process): expose spawn failures from the capture owner
  • 6f863f6 fix(process): classify spawn failures consistently across consumers
  • 3226dec fix(process): report host spawn capacity as a retryable constraint
  • 62d04ed docs(adb): describe host capacity failures from device operations
  • 750aef7 Merge remote-tracking branch 'origin/main' into HEAD
  • 454f017 fix(adb): report exhausted host spawn capacity as runtime unavailability
  • 3a964f0 fix(adb): report probe-time host capacity as a retryable constraint

📊 Changes

12 files changed (+765 additions, -73 deletions)

View changed files

📝 docs/adb-device-analysis.md (+20 -4)
📝 src/adb/AdbCommand.test.ts (+58 -0)
📝 src/adb/AdbCommand.ts (+34 -4)
📝 src/adb/AdbConfiguration.ts (+58 -24)
📝 src/adb/AdbProvider.test.ts (+98 -0)
📝 src/adb/AdbProvider.ts (+24 -3)
📝 src/adb/AdbVersionProbe.test.ts (+86 -1)
📝 src/filesystem/RegularFile.ts (+10 -1)
📝 src/process/ExecFileOutput.test.ts (+79 -0)
📝 src/process/ExecFileOutput.ts (+91 -35)
📝 tests/boundary/cli/adbCli.test.ts (+108 -1)
➕ tests/boundary/process/adbDescriptorPressure.test.ts (+99 -0)

📄 Description

Summary

Follows up #5689, which main closed with 57e3e0b04 (an EACCES/EPERM adb now names its path). No open issue tracks the two cases that remain:

  • A path that cannot resolve because a parent is a regular file (ENOTDIR) still reports only "The selected adb binary did not report a usable version." Node throws that spawn error synchronously, so it never reaches the adb classifier. It now reports the path as missing, like ENOENT.
  • Exhausted host capacity (EAGAIN, EMFILE, ENFILE, ENOMEM) is not separated from a bad path. It now says the host could not start adb and keeps REA_ADB_PATH out of the blame. inspect_adb_client and the device operations (at the version probe or a later adb command) report it as a retryable capacity constraint; the binary_session inventory reports runtime_unavailable.

The remediation for EACCES also now distinguishes REA_ADB_PATH from an adb on PATH.

Fixing that needed the process capture owner to say when a command never started. execFileBytes now marks every spawn failure, including the ones Node throws synchronously (ENOTDIR, EPERM), as spawnFailed. Only the adb readiness probe and adb commands read that fact; the DMG reader, the Darwin compiler probe and assetutil are unchanged from main, as requested in the review.

Problem and expected behavior

  • Trigger: REA_ADB_PATH=/some/file/adb rea inspect-adb-client --json, where /some/file is a regular file (ENOTDIR). Same result for list-adb-devices.
  • Cause: synchronous spawn errors (ENOTDIR, EPERM) were rejected from execFileBytes without capture fields, so execFileOutputFailure returned undefined and no consumer could see their code. inspectAdbClient then fell through to the generic version_unresolved refusal. Host-capacity errnos had no classification and were treated as a refusal of the selected path.
  • Expected: readiness stays refused, but the refusal names the selected path and the actual start failure. It stays distinct from a missing binary, from exhausted host capacity, and from real unrecognized version output.

Change and scope

  • src/process/ExecFileOutput.ts (owner): admits synchronous and asynchronous spawn errno failures with the same empty capture and spawnFailed: true. Argument validation errors stay unadmitted. spawnCapacityExhausted classifies EAGAIN, EMFILE, ENFILE and ENOMEM next to the fact it reads. After an EMFILE or ENFILE spawn failure Node creates no stdio, so the owned stop-signal route (the one adb uses) attaches its error listener first and captures absent streams as empty; before, reading the streams threw a TypeError and the later spawn error went unhandled.
  • inspectAdbClient:
    • Path-resolution errnos (ENOENT, ENOTDIR, ELOOP, ENAMETOOLONG, from the filesystem owner's predicate) stay binary_missing.
    • EACCES/EPERM and other spawn errnos stay binary_not_executable (not_configured, as on main). The remediation now depends on whether adb came from REA_ADB_PATH or PATH. ADB_NOT_EXECUTABLE_REMEDIATION now holds the REA_ADB_PATH EACCES text, so capability discovery reports the same repair as the failure.
    • Host capacity is a new host_capacity failure, mapped to runtime_unavailable for the inventory availability and to the retryable capacity constraint for inspect_adb_client and the device operations. One remediation text, owned by AdbConfiguration (adbHostCapacityRemediation), says the host could not start adb and that REA_ADB_PATH does not need to change; it travels as the failure's remediation (the shape main adopted in #8532), and the constraint's remediation.action uses the same function, with no copy in diagnostics. PROVIDER_REJECTION_CODES is unchanged.
    • A spawn errno is recorded as filesystem_error, never as exit_code. The generic fallback also names the path and path_source.
  • runAdbCapture: a never-started adb says so instead of claiming an exit code; host capacity is a retryable capacity resource constraint (adbSpawnCapacityError, shared with the version probe). The exception is push_adb_file after adb push has completed: a capacity failure starting sha256sum returns the receipt with device_digest_source: "unavailable" and names the failure in its limitations, because repeating the request with overwrite: false would be refused.
  • Not included: the other execFileBytes consumers. An earlier revision moved the DMG reader, the Darwin compiler probe and assetutil onto spawnFailed and gave assetutil a capacity constraint; those changes are reverted so the diff stays on the adb start-failure and capacity cases. They can follow separately if wanted.
  • Not included: with adb from PATH, the message names adb, not the resolved file. Resolving the first PATH match would add a lookup the spawn does not use; the remediation points at PATH.

Follow the contribution scope.
Unresolved scope, authority, side effects, or required verification block
integration. Link the required maintainer scope decision for new providers or
capabilities involving target execution, injection, mutation, or protection
recovery; ordinary fixes within existing boundaries may state "not applicable".
Draft PRs may leave the scope decision pending and the readiness and merge checklists
unchecked. Maintainers can help resolve open questions.

Contract and boundary impact

  • Semantic owner and earliest changed stage: execFileBytes / execFileOutputFailure in src/process/ExecFileOutput.ts; adb classification in inspectAdbClient and runAdbCapture; adbSpawnCapacityError is the one capacity constraint for both the version probe and later adb commands.
  • Contribution scope fit and linked maintainer decision: not applicable; ordinary fix within existing boundaries.
  • CLI and MCP/tool-catalog contract: readiness error code stays capability_unavailable; message and remediation text change. Host capacity from inspect_adb_client and the adb device operations becomes a retryable capacity resource constraint; the inventory availability is runtime_unavailable. No schema change.
  • Provider, bridge, target-format, or platform compatibility: POSIX spawn semantics. Windows not run.
  • Evidence, artifact, provenance, or reconstruction contract: none.
  • Target selection, authority, upstream behavior, and cleanup details (if applicable): not applicable; no device is contacted on the refused paths.
  • Generated metadata (docs/public/product-catalog.json), package, or installation impact (CI generates and validates ignored catalogs and skills/; edit authored instructions in .agents/skills/reverse-engineer-anything/ and never commit build outputs): none.

Expected effects (check known effects; use documentation-only on its own).
Unchecked items do not establish absence; leave uncertainties visible.

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.
  • Change process memory, registers, files, or configuration.
  • Contact network services.
  • Recover protected code or work around protections.
  • Documentation or usability only; no target interaction.
  • Not sure yet; investigation or maintainer guidance needed.

The checked box covers how an existing adb launch reports a failed start. No new process is launched. execFileBytes is shared: on a synchronous spawn throw its other callers now receive the same empty stdout/stderr capture and spawnFailed marker an asynchronous spawn failure carries; code and syscall are unchanged, and those callers read only code/syscall directly (searched, not individually tested). No other consumer reads spawnFailed.

Evidence and regression coverage

  • Tests added or updated:
    • tests/boundary/cli/adbCli.test.ts: directory and 0644 REA_ADB_PATH through inspect-adb-client and list-adb-devices, a chmod 755 success control, and a 0644 adb as the only PATH entry.
    • src/process/ExecFileOutput.test.ts: synchronous ENOTDIR spawn admitted with an empty capture and spawnFailed on both routes, Node's execFile (no stopSignal) and the owned spawn route (stopSignal, the one adb uses); argument validation errors stay unadmitted.
    • src/adb/AdbVersionProbe.test.ts, src/adb/AdbCommand.test.ts: EPERM, ENOTDIR and host-capacity classification, and no exit_code for a never-started adb. The assertions check code, filesystem_error, selected_binary, path_source and the owner's spawnFailed marker, not refusal wording.
    • tests/boundary/process/adbDescriptorPressure.test.ts: real EMFILE under ulimit -n 64 against the built modules. The version probe reports host_capacity with filesystem_error: EMFILE, a device command reports a retryable capacity constraint, no unhandled error reaches stderr, and both succeed after the descriptors are released. Without the stop-signal stdio fix it fails with an unhandled spawn ... EMFILE.
    • src/adb/AdbProvider.test.ts: host capacity at the version probe of list_adb_devices is a retryable resource_constraint that keeps selected_binary, path_source and filesystem_error, not capability_unavailable (fails with the AdbProvider.ts change reverted).
    • With src/adb/AdbConfiguration.ts reverted to main (1173c0a9d), the two new CLI tests fail (2 failed, 34 passed). Reverting the host_capacity mapping fails the capacity readiness test. Reverting the runAdbCapture capacity branch fails its capacity test.
  • Base reproduction or other evidence: built CLI E2E below, base main 1173c0a9d and head 454f0177f.
  • User-visible CLI/MCP output (if applicable): below.
  • Remaining proof gaps: a real EPERM, EAGAIN and ENOMEM were not produced; those branches are covered by tests that fake the spawn error. Real EMFILE is produced by the descriptor-pressure test. No device was attached for either run's list-adb-devices control. Windows and Linux not run.

Boundary regressions covered:

  • Missing, empty, and unknown values: a never-started adb is kept distinct from a non-zero exit. The spawn errno is recorded as filesystem_error, never as exit_code, and the empty capture is admitted only for spawn failures; argument validation errors stay unadmitted (ExecFileOutput.test.ts, AdbVersionProbe.test.ts).
  • Public success and error delivery: adbCli.test.ts and the E2E below run inspect-adb-client and list-adb-devices through the built CLI. Gap: MCP was not exercised separately (same refusal projection), and host capacity is covered at the provider and module level, plus real EMFILE against the built modules (adbDescriptorPressure.test.ts), not through the CLI.

Lifecycle regressions: not applicable. A failed spawn acquires no process or resource, and existing cleanup paths are unchanged.

For evidence-bearing changes:

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

E2E: built CLI, real adb 37.0.1

$WORK is a fresh temp directory. The 0644 files are copies of the real platform-tools adb. Output is reduced with jq to {code, message} for errors and a summary for successes.

Only ENOTDIR changes the outcome. The EACCES rows differ only in remediation wording. Controls are identical.

Base (origin/main 1173c0a9d)
# Base (origin/main) — 1173c0a9d, macOS 26.5.2, node v24.22.1, Version 37.0.1-15733141

$ REA_ADB_PATH=$WORK/adb-dir rea inspect-adb-client --json   # directory
{"code":"capability_unavailable","message":"Execution of the selected adb binary was denied (EACCES): $WORK/adb-dir. Check file type and execute permission for REA_ADB_PATH or select an executable adb binary."}
exit=1

$ REA_ADB_PATH=$WORK/adb-noexec rea inspect-adb-client --json   # real adb copy, mode 0644
{"code":"capability_unavailable","message":"Execution of the selected adb binary was denied (EACCES): $WORK/adb-noexec. Check file type and execute permission for REA_ADB_PATH or select an executable adb binary."}
exit=1

$ REA_ADB_PATH=$WORK/adb-dir rea list-adb-devices --json   # device operation
{"code":"capability_unavailable","message":"Execution of the selected adb binary was denied (EACCES): $WORK/adb-dir. Check file type and execute permission for REA_ADB_PATH or select an executable adb binary."}
exit=1

$ PATH=$WORK/bin rea inspect-adb-client --json   # REA_ADB_PATH unset, adb on PATH is mode 0644
{"code":"capability_unavailable","message":"Execution of the selected adb binary was denied (EACCES): adb. Check file type and execute permission for REA_ADB_PATH or select an executable adb binary."}
exit=1

$ REA_ADB_PATH=$WORK/notdir/adb rea inspect-adb-client --json   # parent is a regular file (ENOTDIR)
{"code":"capability_unavailable","message":"The selected adb binary did not report a usable version."}
exit=1

$ REA_ADB_PATH=$WORK/notdir/adb rea list-adb-devices --json   # device operation, ENOTDIR
{"code":"capability_unavailable","message":"The selected adb binary did not report a usable version."}
exit=1

$ REA_ADB_PATH=$WORK/missing rea inspect-adb-client --json   # control: missing path
{"code":"capability_unavailable","message":"The selected adb binary was not found: $WORK/missing. Set REA_ADB_PATH to an absolute path to an existing adb binary."}
exit=1

$ chmod 755 $WORK/adb-noexec; REA_ADB_PATH=$WORK/adb-noexec rea inspect-adb-client --json   # control: same file, executable
{"ok":true,"version":"37.0.1-15733141"}
exit=0

$ REA_ADB_PATH=~/Library/Android/sdk/platform-tools/adb rea list-adb-devices --json   # control: real adb
{"ok":true,"version":"37.0.1-15733141"}
exit=0
This branch (454f0177f, main merged)
# This branch — 454f0177f, macOS 26.5.2, node v24.22.1, Version 37.0.1-15733141

$ REA_ADB_PATH=$WORK/adb-dir rea inspect-adb-client --json   # directory
{"code":"capability_unavailable","message":"Execution of the selected adb binary was denied (EACCES): $WORK/adb-dir. Set REA_ADB_PATH to an absolute path to the adb executable file, not a directory, and check execute permission on it and search permission on its parent directories."}
exit=1

$ REA_ADB_PATH=$WORK/adb-noexec rea inspect-adb-client --json   # real adb copy, mode 0644
{"code":"capability_unavailable","message":"Execution of the selected adb binary was denied (EACCES): $WORK/adb-noexec. Set REA_ADB_PATH to an absolute path to the adb executable file, not a directory, and check execute permission on it and search permission on its parent directories."}
exit=1

$ REA_ADB_PATH=$WORK/adb-dir rea list-adb-devices --json   # device operation
{"code":"capability_unavailable","message":"Execution of the selected adb binary was denied (EACCES): $WORK/adb-dir. Set REA_ADB_PATH to an absolute path to the adb executable file, not a directory, and check execute permission on it and search permission on its parent directories."}
exit=1

$ PATH=$WORK/bin rea inspect-adb-client --json   # REA_ADB_PATH unset, adb on PATH is mode 0644
{"code":"capability_unavailable","message":"Execution of the selected adb binary was denied (EACCES): adb. Make sure the adb found on PATH is a file with execute permission, not a directory, and that its parent directories have search permission, or set REA_ADB_PATH to an absolute path to an executable adb binary."}
exit=1

$ REA_ADB_PATH=$WORK/notdir/adb rea inspect-adb-client --json   # parent is a regular file (ENOTDIR)
{"code":"capability_unavailable","message":"The selected adb binary was not found: $WORK/notdir/adb. Set REA_ADB_PATH to an absolute path to an existing adb binary."}
exit=1

$ REA_ADB_PATH=$WORK/notdir/adb rea list-adb-devices --json   # device operation, ENOTDIR
{"code":"capability_unavailable","message":"The selected adb binary was not found: $WORK/notdir/adb. Set REA_ADB_PATH to an absolute path to an existing adb binary."}
exit=1

$ REA_ADB_PATH=$WORK/missing rea inspect-adb-client --json   # control: missing path
{"code":"capability_unavailable","message":"The selected adb binary was not found: $WORK/missing. Set REA_ADB_PATH to an absolute path to an existing adb binary."}
exit=1

$ chmod 755 $WORK/adb-noexec; REA_ADB_PATH=$WORK/adb-noexec rea inspect-adb-client --json   # control: same file, executable
{"ok":true,"version":"37.0.1-15733141"}
exit=0

$ REA_ADB_PATH=~/Library/Android/sdk/platform-tools/adb rea list-adb-devices --json   # control: real adb
{"ok":true,"version":"37.0.1-15733141"}
exit=0

Validation performed

At 16b272d00 (main 71f680d7c merged; macOS 26.5.2, Node 24.22.1):

  • Conflicts in src/adb/AdbCommand.ts and src/adb/AdbCommand.test.ts with main's adb startup-notice handling. Both sides' imports and tests are kept; the test file has no lines removed relative to main.
  • npm run test:focused -- AdbCommand, AdbProvider, AdbVersionProbe, ExecFileOutput, tests/boundary/cli/adbCli.test.ts, adbDescriptorPressure — 6 files, 237 passed.
  • npm run check:fast, npm run format:check and npm run docs:check — passed. The Static checks failure at a3f2d6caf was format:check on website/wrangler.toml, which this PR doesn't touch; main has since fixed it. CI on this head is still pending.
  • Built CLI: REA_ADB_PATH=/etc/passwd/adb rea inspect-adb-client --json reports capability_unavailable, "The selected adb binary was not found: ..."; with the real adb (37.0.1), inspect-adb-client succeeds.
  • Not run on this head: full npm run check:pr, a real device, Linux, Windows.

At 8151d0a7d (main 8250bf232 merged; macOS 26.5.2, Node 24.22.1):

  • The commit hook on c68f0dab5 had reformatted 23 unrelated files. This merge restores them to main's content, so the diff against main is again the 12 adb and spawn-failure files. That merge commit used --no-verify so the hook would not reapply those changes; the pre-push check:fast ran.
  • npm run test:focused -- the same 8 files as below — 240 passed
  • npm run check:fast and npm run docs:check — passed
  • CI: Native package baseline (darwin arm64) failed at c68f0dab5 and 3b927674c with malformed_procargs_path_padding during verifier cleanup, at different stages (package install, package update). feat/owned-debugger-process-lifecycle failed the same job with the same diagnostic at b21c2a19a and passed at its next commit. The same job passed at 8151d0a7d, whose only change from 3b927674c is the merge above. The cause of the two failures is unconfirmed: the ownership reader's helper path uses ExecFileOutput, which this PR changes, so one pass does not exclude this PR.
  • Local npm run verify:package at 3b927674c and main 8250bf232: 3 of 3 rounds passed on both (macOS arm64, a different host from the CI runner). This did not reproduce the failure, and does not establish that it is unrelated.

At 3b927674c (no new merge; macOS 26.5.2, Node 24.22.1):

  • npm run test:focused -- the same 8 files as below — 240 passed
  • New AdbProvider.test.ts case for a capacity failure at the digest check, with a faked EMFILE spawn error — fails with src/adb/AdbProvider.ts reverted (resource_constraint, retryable: true), passes with the change
  • npm run check:fast and npm run docs:check — passed
  • Not run: the repeated overwrite: false request (source-derived), a real device, a real EMFILE at this step, Linux, Windows.

At c68f0dab5 (main d3ab0faf5 merged; conflict in AdbConfiguration.ts with #8532 resolved by passing each repair as the failure's remediation instead of diagnostics.remediation; macOS 26.5.2, Node 24.22.1):

  • npm run test:focused -- AdbConfiguration, AdbVersionProbe, AdbCommand, AdbProvider, ExecFileOutput, adbDescriptorPressure, tests/boundary/cli/adbCli.test.ts, tests/boundary/mcp/capabilityRemediationMcp.test.ts — 8 files, 239 passed
  • npm run check:fast and npm run docs:check — passed
  • Built CLI: REA_ADB_PATH=<regular file>/adb rea inspect-adb-client --json reports capability_unavailable, "The selected adb binary was not found: ..." and the REA_ADB_PATH repair
  • Not run on this head: full npm run check:pr, Linux, Windows. main has since moved to 8250bf232; that merge is textually clean and its two commits do not touch src/adb or src/process.

At 02bc03d9f (main 317bf3fe2 merged cleanly; stop-signal stdio fix in b08e27e77; macOS 26.5.2, Node 24.22.1):

  • npm run test:focused -- adbDescriptorPressure, ExecFileOutput, AdbCommand, AdbProvider, AdbVersionProbe, tests/boundary/cli/adbCli.test.ts — 6 files, 228 passed
  • adbDescriptorPressure.test.ts with src/process/ExecFileOutput.ts reverted to the previous head — fails with an unhandled spawn ... EMFILE
  • npm run check:fast — passed
  • npm run format:check — passed
  • The previous CI run at 7495a3440 failed in bundleComparisonCli/bundleComparisonMcp and flutterMcp, which also failed on main d3ecf6800; bundleComparisonCli and bundleComparisonMcp pass locally after this merge (2 files, 12 passed)

At d90f12364 (main 18a8e71c9 merged; main 2b8fb387b made resolveAdbBinary asynchronous and added Windows PATH resolution in AdbConfiguration.ts, no conflict with the spawn-failure classification; macOS 26.5.2, Node 24.22.1):

  • npm run test:focused -- AdbCommand, AdbProvider, AdbVersionProbe, AdbConfiguration, ExecFileOutput, tests/boundary/cli/adbCli.test.ts — 6 files, 225 passed
  • npm run check:fast — passed
  • npm run docs:check — passed

At 0da410cd4 (merge base main a95b83c2b unchanged; macOS 26.5.2, Node 24.22.1):

  • npm run test:focused -- AdbCommand, AdbProvider, AdbVersionProbe, ExecFileOutput, tests/boundary/cli/adbCli.test.ts — 5 files, 218 passed (the synchronous spawn-throw case now runs on both routes; inspect_adb_client added to the host-capacity provider test)
  • npm run check:fast — passed
  • npm run docs:check — passed

At 180caed03 (main a95b83c2b merged cleanly; macOS 26.5.2, Node 24.22.1):

  • npm run test:local -- AdbCommand, AdbProvider, AdbVersionProbe, ExecFileOutput — 181 passed
  • npm run test:focused -- tests/boundary/cli/adbCli.test.ts — 36 passed
  • npm run check:fast — passed
  • npm run docs:check — passed

At d1c9203fe (main 365fb03e7 merged; execFileBytes keeps main's child.stdin?.end() inside the synchronous-spawn guard; macOS 26.5.2, Node 24.22.1):

  • npm run test:local -- all src/adb/*.test.ts and ExecFileOutput — 287 passed
  • npm run test:focused -- tests/boundary/cli/adbCli.test.ts — 36 passed
  • npm run check:fast — passed
  • npm run docs:check — passed

At 06af91993 (main 8706068b5 merged; test assertions moved to facts in 9737b49e2; macOS 26.5.2, Node 24.22.1):

  • npm run check:fast — passed
  • npm run docs:check — passed
  • npm run test:local -- all src/adb/*.test.ts and ExecFileOutput — 6 files, 278 passed
  • npm run test:focused -- tests/boundary/cli/adbCli.test.ts — 36 passed
  • main's ExecFileOutput refactor (startStoppableCommand) is merged; the synchronous spawn admission now wraps both the execFile and the stoppable spawn start and completes through the shared complete callback.
  • Not caused by this PR: tests/composition/analysis-sessions/providerBinding.test.ts (1 failed) and tests/boundary/filesystem/safeOutputTreeCloseFailures.test.ts (2 failed) fail identically on an unmodified main checkout at 29ebd6740 on this machine, and they were the failures in this PR's CI run at 6f92e8c22 (Source tests, Portable tests). This PR touches neither.

At 6f92e8c22 (main fc7f042c8 merged, macOS 26.5.2, Node 24.22.1):

  • npm run check:fast — passed
  • npm run docs:check — passed
  • npm run test:local -- all src/adb/*.test.ts, ExecFileOutput, assetutil (now main's) — 9 files, 309 passed, 1 skipped (Windows-only skip)
  • npm run test:focused -- adbCli, Darwin token reader and DMG reader boundary files (the latter two now main's) — 3 files, 121 passed
  • Earlier, at 8752d6e79: src/adb/AdbProvider.ts reverted — the probe-capacity test fails; at 454f0177f: src/adb/AdbConfiguration.ts reverted to main — the two new CLI tests fail; host_capacity mapping reverted — the capacity readiness test fails
  • Built-CLI E2E above ran at 454f0177f with adb 37.0.1; the adb classification is unchanged since, so it was not rerun. Not run: full npm run check:pr, Linux, Windows.

Compatibility, safety, and release

  • Breaking changes or migration steps: none. Same readiness error code; refusal text is more specific. Host capacity from adb device operations is now a retryable resource constraint instead of a protocol or unavailable error.
  • Real Hopper/Ghidra, browser, or OS coverage: macOS only, with a real adb client.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: the message echoes the caller-selected path, as the existing missing-path refusal already does. No new process is started.

Author readiness

Human owner: @chordpli

  • I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
  • I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
  • I declare this PR ready for initial maintainer review.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/6188 **Author:** [@chordpli](https://github.com/chordpli) **Created:** 10/21/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `fix/adb-client-unusable-path` --- ### 📝 Commits (10+) - [`0d29c0b`](https://github.com/morluto/rea/commit/0d29c0b3c3f2037a9b7c35737b3eab6f2299f264) fix(adb): name unexecutable adb paths in readiness refusals - [`dbfe4f7`](https://github.com/morluto/rea/commit/dbfe4f769ee30aeb61afad0e22ace0d4aac3b0bd) Merge remote-tracking branch 'origin/main' into fix/adb-client-unusable-path - [`f148871`](https://github.com/morluto/rea/commit/f14887137a2b2a99f4a418e9daeb71b55a1ae611) fix(process): admit synchronous spawn errors as captured failures - [`b3838ba`](https://github.com/morluto/rea/commit/b3838ba3df9faae07fd4ceb8c116be0d50d81bf7) fix(process): expose spawn failures from the capture owner - [`6f863f6`](https://github.com/morluto/rea/commit/6f863f6dd2e6a9357efa39731b758cb6d13a86d0) fix(process): classify spawn failures consistently across consumers - [`3226dec`](https://github.com/morluto/rea/commit/3226dec227fc31b20d517f24080a5fa8edee3b74) fix(process): report host spawn capacity as a retryable constraint - [`62d04ed`](https://github.com/morluto/rea/commit/62d04edd0d538cce75fbff64fca7825d46d378e0) docs(adb): describe host capacity failures from device operations - [`750aef7`](https://github.com/morluto/rea/commit/750aef7ca56426e2c954db471da3fd7e2bc755f5) Merge remote-tracking branch 'origin/main' into HEAD - [`454f017`](https://github.com/morluto/rea/commit/454f0177f49aea53bcb3414f05284d2cef16b611) fix(adb): report exhausted host spawn capacity as runtime unavailability - [`3a964f0`](https://github.com/morluto/rea/commit/3a964f0d84c44891feacd7b157f4f8d8dab7124e) fix(adb): report probe-time host capacity as a retryable constraint ### 📊 Changes **12 files changed** (+765 additions, -73 deletions) <details> <summary>View changed files</summary> 📝 `docs/adb-device-analysis.md` (+20 -4) 📝 `src/adb/AdbCommand.test.ts` (+58 -0) 📝 `src/adb/AdbCommand.ts` (+34 -4) 📝 `src/adb/AdbConfiguration.ts` (+58 -24) 📝 `src/adb/AdbProvider.test.ts` (+98 -0) 📝 `src/adb/AdbProvider.ts` (+24 -3) 📝 `src/adb/AdbVersionProbe.test.ts` (+86 -1) 📝 `src/filesystem/RegularFile.ts` (+10 -1) 📝 `src/process/ExecFileOutput.test.ts` (+79 -0) 📝 `src/process/ExecFileOutput.ts` (+91 -35) 📝 `tests/boundary/cli/adbCli.test.ts` (+108 -1) ➕ `tests/boundary/process/adbDescriptorPressure.test.ts` (+99 -0) </details> ### 📄 Description ## Summary Follows up #5689, which `main` closed with `57e3e0b04` (an `EACCES`/`EPERM` adb now names its path). No open issue tracks the two cases that remain: - A path that cannot resolve because a parent is a regular file (`ENOTDIR`) still reports only "The selected adb binary did not report a usable version." Node throws that spawn error synchronously, so it never reaches the adb classifier. It now reports the path as missing, like `ENOENT`. - Exhausted host capacity (`EAGAIN`, `EMFILE`, `ENFILE`, `ENOMEM`) is not separated from a bad path. It now says the host could not start adb and keeps `REA_ADB_PATH` out of the blame. `inspect_adb_client` and the device operations (at the version probe or a later adb command) report it as a retryable `capacity` constraint; the `binary_session` inventory reports `runtime_unavailable`. The remediation for `EACCES` also now distinguishes `REA_ADB_PATH` from an `adb` on `PATH`. Fixing that needed the process capture owner to say when a command never started. `execFileBytes` now marks every spawn failure, including the ones Node throws synchronously (`ENOTDIR`, `EPERM`), as `spawnFailed`. Only the adb readiness probe and adb commands read that fact; the DMG reader, the Darwin compiler probe and assetutil are unchanged from `main`, as requested in the review. ## Problem and expected behavior - Trigger: `REA_ADB_PATH=/some/file/adb rea inspect-adb-client --json`, where `/some/file` is a regular file (`ENOTDIR`). Same result for `list-adb-devices`. - Cause: synchronous spawn errors (`ENOTDIR`, `EPERM`) were rejected from `execFileBytes` without capture fields, so `execFileOutputFailure` returned `undefined` and no consumer could see their code. `inspectAdbClient` then fell through to the generic `version_unresolved` refusal. Host-capacity errnos had no classification and were treated as a refusal of the selected path. - Expected: readiness stays refused, but the refusal names the selected path and the actual start failure. It stays distinct from a missing binary, from exhausted host capacity, and from real unrecognized version output. ## Change and scope - `src/process/ExecFileOutput.ts` (owner): admits synchronous and asynchronous spawn errno failures with the same empty capture and `spawnFailed: true`. Argument validation errors stay unadmitted. `spawnCapacityExhausted` classifies `EAGAIN`, `EMFILE`, `ENFILE` and `ENOMEM` next to the fact it reads. After an `EMFILE` or `ENFILE` spawn failure Node creates no stdio, so the owned stop-signal route (the one adb uses) attaches its error listener first and captures absent streams as empty; before, reading the streams threw a `TypeError` and the later spawn error went unhandled. - `inspectAdbClient`: - Path-resolution errnos (`ENOENT`, `ENOTDIR`, `ELOOP`, `ENAMETOOLONG`, from the filesystem owner's predicate) stay `binary_missing`. - `EACCES`/`EPERM` and other spawn errnos stay `binary_not_executable` (`not_configured`, as on `main`). The remediation now depends on whether adb came from `REA_ADB_PATH` or `PATH`. `ADB_NOT_EXECUTABLE_REMEDIATION` now holds the `REA_ADB_PATH` `EACCES` text, so capability discovery reports the same repair as the failure. - Host capacity is a new `host_capacity` failure, mapped to `runtime_unavailable` for the inventory availability and to the retryable `capacity` constraint for `inspect_adb_client` and the device operations. One remediation text, owned by `AdbConfiguration` (`adbHostCapacityRemediation`), says the host could not start adb and that `REA_ADB_PATH` does not need to change; it travels as the failure's `remediation` (the shape `main` adopted in #8532), and the constraint's `remediation.action` uses the same function, with no copy in `diagnostics`. `PROVIDER_REJECTION_CODES` is unchanged. - A spawn errno is recorded as `filesystem_error`, never as `exit_code`. The generic fallback also names the path and `path_source`. - `runAdbCapture`: a never-started adb says so instead of claiming an exit code; host capacity is a retryable `capacity` resource constraint (`adbSpawnCapacityError`, shared with the version probe). The exception is `push_adb_file` after `adb push` has completed: a capacity failure starting `sha256sum` returns the receipt with `device_digest_source: "unavailable"` and names the failure in its limitations, because repeating the request with `overwrite: false` would be refused. - Not included: the other `execFileBytes` consumers. An earlier revision moved the DMG reader, the Darwin compiler probe and assetutil onto `spawnFailed` and gave assetutil a capacity constraint; those changes are reverted so the diff stays on the adb start-failure and capacity cases. They can follow separately if wanted. - Not included: with adb from `PATH`, the message names `adb`, not the resolved file. Resolving the first `PATH` match would add a lookup the spawn does not use; the remediation points at `PATH`. Follow the [contribution scope](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#contribution-scope). Unresolved scope, authority, side effects, or required verification block integration. Link the required maintainer scope decision for new providers or capabilities involving target execution, injection, mutation, or protection recovery; ordinary fixes within existing boundaries may state "not applicable". Draft PRs may leave the scope decision pending and the readiness and merge checklists unchecked. Maintainers can help resolve open questions. ## Contract and boundary impact - Semantic owner and earliest changed stage: `execFileBytes` / `execFileOutputFailure` in `src/process/ExecFileOutput.ts`; adb classification in `inspectAdbClient` and `runAdbCapture`; `adbSpawnCapacityError` is the one capacity constraint for both the version probe and later adb commands. - Contribution scope fit and linked maintainer decision: not applicable; ordinary fix within existing boundaries. - CLI and MCP/tool-catalog contract: readiness error code stays `capability_unavailable`; message and remediation text change. Host capacity from `inspect_adb_client` and the adb device operations becomes a retryable `capacity` resource constraint; the inventory availability is `runtime_unavailable`. No schema change. - Provider, bridge, target-format, or platform compatibility: POSIX spawn semantics. Windows not run. - Evidence, artifact, provenance, or reconstruction contract: none. - Target selection, authority, upstream behavior, and cleanup details (if applicable): not applicable; no device is contacted on the refused paths. - Generated metadata (`docs/public/product-catalog.json`), package, or installation impact (CI generates and validates ignored catalogs and `skills/`; edit authored instructions in `.agents/skills/reverse-engineer-anything/` and never commit build outputs): none. Expected effects (check known effects; use documentation-only on its own). Unchecked items do not establish absence; leave uncertainties visible. - [ ] Read existing files or captured data. - [x] Launch or attach to a process. - [ ] Inject instrumentation or run scripts/code. - [ ] Change process memory, registers, files, or configuration. - [ ] Contact network services. - [ ] Recover protected code or work around protections. - [ ] Documentation or usability only; no target interaction. - [ ] Not sure yet; investigation or maintainer guidance needed. The checked box covers how an existing adb launch reports a failed start. No new process is launched. `execFileBytes` is shared: on a synchronous spawn throw its other callers now receive the same empty `stdout`/`stderr` capture and `spawnFailed` marker an asynchronous spawn failure carries; `code` and `syscall` are unchanged, and those callers read only `code`/`syscall` directly (searched, not individually tested). No other consumer reads `spawnFailed`. ## Evidence and regression coverage - Tests added or updated: - `tests/boundary/cli/adbCli.test.ts`: directory and `0644` `REA_ADB_PATH` through `inspect-adb-client` and `list-adb-devices`, a `chmod 755` success control, and a `0644` `adb` as the only `PATH` entry. - `src/process/ExecFileOutput.test.ts`: synchronous `ENOTDIR` spawn admitted with an empty capture and `spawnFailed` on both routes, Node's `execFile` (no `stopSignal`) and the owned `spawn` route (`stopSignal`, the one adb uses); argument validation errors stay unadmitted. - `src/adb/AdbVersionProbe.test.ts`, `src/adb/AdbCommand.test.ts`: `EPERM`, `ENOTDIR` and host-capacity classification, and no `exit_code` for a never-started adb. The assertions check `code`, `filesystem_error`, `selected_binary`, `path_source` and the owner's `spawnFailed` marker, not refusal wording. - `tests/boundary/process/adbDescriptorPressure.test.ts`: real `EMFILE` under `ulimit -n 64` against the built modules. The version probe reports `host_capacity` with `filesystem_error: EMFILE`, a device command reports a retryable `capacity` constraint, no unhandled error reaches stderr, and both succeed after the descriptors are released. Without the stop-signal stdio fix it fails with an unhandled `spawn ... EMFILE`. - `src/adb/AdbProvider.test.ts`: host capacity at the version probe of `list_adb_devices` is a retryable `resource_constraint` that keeps `selected_binary`, `path_source` and `filesystem_error`, not `capability_unavailable` (fails with the `AdbProvider.ts` change reverted). - With `src/adb/AdbConfiguration.ts` reverted to `main` (`1173c0a9d`), the two new CLI tests fail (2 failed, 34 passed). Reverting the `host_capacity` mapping fails the capacity readiness test. Reverting the `runAdbCapture` capacity branch fails its capacity test. - Base reproduction or other evidence: built CLI E2E below, base `main` `1173c0a9d` and head `454f0177f`. - User-visible CLI/MCP output (if applicable): below. - Remaining proof gaps: a real `EPERM`, `EAGAIN` and `ENOMEM` were not produced; those branches are covered by tests that fake the spawn error. Real `EMFILE` is produced by the descriptor-pressure test. No device was attached for either run's `list-adb-devices` control. Windows and Linux not run. Boundary regressions covered: - Missing, empty, and unknown values: a never-started adb is kept distinct from a non-zero exit. The spawn errno is recorded as `filesystem_error`, never as `exit_code`, and the empty capture is admitted only for spawn failures; argument validation errors stay unadmitted (`ExecFileOutput.test.ts`, `AdbVersionProbe.test.ts`). - Public success and error delivery: `adbCli.test.ts` and the E2E below run `inspect-adb-client` and `list-adb-devices` through the built CLI. Gap: MCP was not exercised separately (same refusal projection), and host capacity is covered at the provider and module level, plus real `EMFILE` against the built modules (`adbDescriptorPressure.test.ts`), not through the CLI. Lifecycle regressions: not applicable. A failed spawn acquires no process or resource, and existing cleanup paths are unchanged. For evidence-bearing changes: - [ ] Observed, derived, and inferred claims remain distinguishable. - [ ] Artifact identity, source provenance, and failed attempts remain preserved. - [ ] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ### E2E: built CLI, real adb 37.0.1 `$WORK` is a fresh temp directory. The `0644` files are copies of the real platform-tools adb. Output is reduced with `jq` to `{code, message}` for errors and a summary for successes. Only `ENOTDIR` changes the outcome. The `EACCES` rows differ only in remediation wording. Controls are identical. <details open> <summary>Base (origin/main <code>1173c0a9d</code>)</summary> ```text # Base (origin/main) — 1173c0a9d, macOS 26.5.2, node v24.22.1, Version 37.0.1-15733141 $ REA_ADB_PATH=$WORK/adb-dir rea inspect-adb-client --json # directory {"code":"capability_unavailable","message":"Execution of the selected adb binary was denied (EACCES): $WORK/adb-dir. Check file type and execute permission for REA_ADB_PATH or select an executable adb binary."} exit=1 $ REA_ADB_PATH=$WORK/adb-noexec rea inspect-adb-client --json # real adb copy, mode 0644 {"code":"capability_unavailable","message":"Execution of the selected adb binary was denied (EACCES): $WORK/adb-noexec. Check file type and execute permission for REA_ADB_PATH or select an executable adb binary."} exit=1 $ REA_ADB_PATH=$WORK/adb-dir rea list-adb-devices --json # device operation {"code":"capability_unavailable","message":"Execution of the selected adb binary was denied (EACCES): $WORK/adb-dir. Check file type and execute permission for REA_ADB_PATH or select an executable adb binary."} exit=1 $ PATH=$WORK/bin rea inspect-adb-client --json # REA_ADB_PATH unset, adb on PATH is mode 0644 {"code":"capability_unavailable","message":"Execution of the selected adb binary was denied (EACCES): adb. Check file type and execute permission for REA_ADB_PATH or select an executable adb binary."} exit=1 $ REA_ADB_PATH=$WORK/notdir/adb rea inspect-adb-client --json # parent is a regular file (ENOTDIR) {"code":"capability_unavailable","message":"The selected adb binary did not report a usable version."} exit=1 $ REA_ADB_PATH=$WORK/notdir/adb rea list-adb-devices --json # device operation, ENOTDIR {"code":"capability_unavailable","message":"The selected adb binary did not report a usable version."} exit=1 $ REA_ADB_PATH=$WORK/missing rea inspect-adb-client --json # control: missing path {"code":"capability_unavailable","message":"The selected adb binary was not found: $WORK/missing. Set REA_ADB_PATH to an absolute path to an existing adb binary."} exit=1 $ chmod 755 $WORK/adb-noexec; REA_ADB_PATH=$WORK/adb-noexec rea inspect-adb-client --json # control: same file, executable {"ok":true,"version":"37.0.1-15733141"} exit=0 $ REA_ADB_PATH=~/Library/Android/sdk/platform-tools/adb rea list-adb-devices --json # control: real adb {"ok":true,"version":"37.0.1-15733141"} exit=0 ``` </details> <details open> <summary>This branch (<code>454f0177f</code>, <code>main</code> merged)</summary> ```text # This branch — 454f0177f, macOS 26.5.2, node v24.22.1, Version 37.0.1-15733141 $ REA_ADB_PATH=$WORK/adb-dir rea inspect-adb-client --json # directory {"code":"capability_unavailable","message":"Execution of the selected adb binary was denied (EACCES): $WORK/adb-dir. Set REA_ADB_PATH to an absolute path to the adb executable file, not a directory, and check execute permission on it and search permission on its parent directories."} exit=1 $ REA_ADB_PATH=$WORK/adb-noexec rea inspect-adb-client --json # real adb copy, mode 0644 {"code":"capability_unavailable","message":"Execution of the selected adb binary was denied (EACCES): $WORK/adb-noexec. Set REA_ADB_PATH to an absolute path to the adb executable file, not a directory, and check execute permission on it and search permission on its parent directories."} exit=1 $ REA_ADB_PATH=$WORK/adb-dir rea list-adb-devices --json # device operation {"code":"capability_unavailable","message":"Execution of the selected adb binary was denied (EACCES): $WORK/adb-dir. Set REA_ADB_PATH to an absolute path to the adb executable file, not a directory, and check execute permission on it and search permission on its parent directories."} exit=1 $ PATH=$WORK/bin rea inspect-adb-client --json # REA_ADB_PATH unset, adb on PATH is mode 0644 {"code":"capability_unavailable","message":"Execution of the selected adb binary was denied (EACCES): adb. Make sure the adb found on PATH is a file with execute permission, not a directory, and that its parent directories have search permission, or set REA_ADB_PATH to an absolute path to an executable adb binary."} exit=1 $ REA_ADB_PATH=$WORK/notdir/adb rea inspect-adb-client --json # parent is a regular file (ENOTDIR) {"code":"capability_unavailable","message":"The selected adb binary was not found: $WORK/notdir/adb. Set REA_ADB_PATH to an absolute path to an existing adb binary."} exit=1 $ REA_ADB_PATH=$WORK/notdir/adb rea list-adb-devices --json # device operation, ENOTDIR {"code":"capability_unavailable","message":"The selected adb binary was not found: $WORK/notdir/adb. Set REA_ADB_PATH to an absolute path to an existing adb binary."} exit=1 $ REA_ADB_PATH=$WORK/missing rea inspect-adb-client --json # control: missing path {"code":"capability_unavailable","message":"The selected adb binary was not found: $WORK/missing. Set REA_ADB_PATH to an absolute path to an existing adb binary."} exit=1 $ chmod 755 $WORK/adb-noexec; REA_ADB_PATH=$WORK/adb-noexec rea inspect-adb-client --json # control: same file, executable {"ok":true,"version":"37.0.1-15733141"} exit=0 $ REA_ADB_PATH=~/Library/Android/sdk/platform-tools/adb rea list-adb-devices --json # control: real adb {"ok":true,"version":"37.0.1-15733141"} exit=0 ``` </details> ## Validation performed At `16b272d00` (`main` `71f680d7c` merged; macOS 26.5.2, Node 24.22.1): - Conflicts in `src/adb/AdbCommand.ts` and `src/adb/AdbCommand.test.ts` with `main`'s adb startup-notice handling. Both sides' imports and tests are kept; the test file has no lines removed relative to `main`. - `npm run test:focused --` `AdbCommand`, `AdbProvider`, `AdbVersionProbe`, `ExecFileOutput`, `tests/boundary/cli/adbCli.test.ts`, `adbDescriptorPressure` — 6 files, 237 passed. - `npm run check:fast`, `npm run format:check` and `npm run docs:check` — passed. The `Static checks` failure at `a3f2d6caf` was `format:check` on `website/wrangler.toml`, which this PR doesn't touch; `main` has since fixed it. CI on this head is still pending. - Built CLI: `REA_ADB_PATH=/etc/passwd/adb rea inspect-adb-client --json` reports `capability_unavailable`, "The selected adb binary was not found: ..."; with the real adb (37.0.1), `inspect-adb-client` succeeds. - Not run on this head: full `npm run check:pr`, a real device, Linux, Windows. At `8151d0a7d` (`main` `8250bf232` merged; macOS 26.5.2, Node 24.22.1): - The commit hook on `c68f0dab5` had reformatted 23 unrelated files. This merge restores them to `main`'s content, so the diff against `main` is again the 12 adb and spawn-failure files. That merge commit used `--no-verify` so the hook would not reapply those changes; the pre-push `check:fast` ran. - `npm run test:focused --` the same 8 files as below — 240 passed - `npm run check:fast` and `npm run docs:check` — passed - CI: `Native package baseline (darwin arm64)` failed at `c68f0dab5` and `3b927674c` with `malformed_procargs_path_padding` during verifier cleanup, at different stages (`package install`, `package update`). `feat/owned-debugger-process-lifecycle` failed the same job with the same diagnostic at `b21c2a19a` and passed at its next commit. The same job passed at `8151d0a7d`, whose only change from `3b927674c` is the merge above. The cause of the two failures is unconfirmed: the ownership reader's helper path uses `ExecFileOutput`, which this PR changes, so one pass does not exclude this PR. - Local `npm run verify:package` at `3b927674c` and `main` `8250bf232`: 3 of 3 rounds passed on both (macOS arm64, a different host from the CI runner). This did not reproduce the failure, and does not establish that it is unrelated. At `3b927674c` (no new merge; macOS 26.5.2, Node 24.22.1): - `npm run test:focused --` the same 8 files as below — 240 passed - New `AdbProvider.test.ts` case for a capacity failure at the digest check, with a faked `EMFILE` spawn error — fails with `src/adb/AdbProvider.ts` reverted (`resource_constraint`, `retryable: true`), passes with the change - `npm run check:fast` and `npm run docs:check` — passed - Not run: the repeated `overwrite: false` request (source-derived), a real device, a real `EMFILE` at this step, Linux, Windows. At `c68f0dab5` (`main` `d3ab0faf5` merged; conflict in `AdbConfiguration.ts` with #8532 resolved by passing each repair as the failure's `remediation` instead of `diagnostics.remediation`; macOS 26.5.2, Node 24.22.1): - `npm run test:focused --` `AdbConfiguration`, `AdbVersionProbe`, `AdbCommand`, `AdbProvider`, `ExecFileOutput`, `adbDescriptorPressure`, `tests/boundary/cli/adbCli.test.ts`, `tests/boundary/mcp/capabilityRemediationMcp.test.ts` — 8 files, 239 passed - `npm run check:fast` and `npm run docs:check` — passed - Built CLI: `REA_ADB_PATH=<regular file>/adb rea inspect-adb-client --json` reports `capability_unavailable`, "The selected adb binary was not found: ..." and the `REA_ADB_PATH` repair - Not run on this head: full `npm run check:pr`, Linux, Windows. `main` has since moved to `8250bf232`; that merge is textually clean and its two commits do not touch `src/adb` or `src/process`. At `02bc03d9f` (`main` `317bf3fe2` merged cleanly; stop-signal stdio fix in `b08e27e77`; macOS 26.5.2, Node 24.22.1): - `npm run test:focused --` `adbDescriptorPressure`, `ExecFileOutput`, `AdbCommand`, `AdbProvider`, `AdbVersionProbe`, `tests/boundary/cli/adbCli.test.ts` — 6 files, 228 passed - `adbDescriptorPressure.test.ts` with `src/process/ExecFileOutput.ts` reverted to the previous head — fails with an unhandled `spawn ... EMFILE` - `npm run check:fast` — passed - `npm run format:check` — passed - The previous CI run at `7495a3440` failed in `bundleComparisonCli`/`bundleComparisonMcp` and `flutterMcp`, which also failed on `main` `d3ecf6800`; `bundleComparisonCli` and `bundleComparisonMcp` pass locally after this merge (2 files, 12 passed) At `d90f12364` (`main` `18a8e71c9` merged; `main` 2b8fb387b made `resolveAdbBinary` asynchronous and added Windows PATH resolution in `AdbConfiguration.ts`, no conflict with the spawn-failure classification; macOS 26.5.2, Node 24.22.1): - `npm run test:focused --` `AdbCommand`, `AdbProvider`, `AdbVersionProbe`, `AdbConfiguration`, `ExecFileOutput`, `tests/boundary/cli/adbCli.test.ts` — 6 files, 225 passed - `npm run check:fast` — passed - `npm run docs:check` — passed At `0da410cd4` (merge base `main` `a95b83c2b` unchanged; macOS 26.5.2, Node 24.22.1): - `npm run test:focused --` `AdbCommand`, `AdbProvider`, `AdbVersionProbe`, `ExecFileOutput`, `tests/boundary/cli/adbCli.test.ts` — 5 files, 218 passed (the synchronous spawn-throw case now runs on both routes; `inspect_adb_client` added to the host-capacity provider test) - `npm run check:fast` — passed - `npm run docs:check` — passed At `180caed03` (`main` `a95b83c2b` merged cleanly; macOS 26.5.2, Node 24.22.1): - `npm run test:local --` `AdbCommand`, `AdbProvider`, `AdbVersionProbe`, `ExecFileOutput` — 181 passed - `npm run test:focused -- tests/boundary/cli/adbCli.test.ts` — 36 passed - `npm run check:fast` — passed - `npm run docs:check` — passed At `d1c9203fe` (`main` `365fb03e7` merged; `execFileBytes` keeps `main`'s `child.stdin?.end()` inside the synchronous-spawn guard; macOS 26.5.2, Node 24.22.1): - `npm run test:local --` all `src/adb/*.test.ts` and `ExecFileOutput` — 287 passed - `npm run test:focused -- tests/boundary/cli/adbCli.test.ts` — 36 passed - `npm run check:fast` — passed - `npm run docs:check` — passed At `06af91993` (`main` `8706068b5` merged; test assertions moved to facts in `9737b49e2`; macOS 26.5.2, Node 24.22.1): - `npm run check:fast` — passed - `npm run docs:check` — passed - `npm run test:local --` all `src/adb/*.test.ts` and `ExecFileOutput` — 6 files, 278 passed - `npm run test:focused -- tests/boundary/cli/adbCli.test.ts` — 36 passed - `main`'s `ExecFileOutput` refactor (`startStoppableCommand`) is merged; the synchronous spawn admission now wraps both the `execFile` and the stoppable `spawn` start and completes through the shared `complete` callback. - Not caused by this PR: `tests/composition/analysis-sessions/providerBinding.test.ts` (1 failed) and `tests/boundary/filesystem/safeOutputTreeCloseFailures.test.ts` (2 failed) fail identically on an unmodified `main` checkout at `29ebd6740` on this machine, and they were the failures in this PR's CI run at `6f92e8c22` (Source tests, Portable tests). This PR touches neither. At `6f92e8c22` (`main` `fc7f042c8` merged, macOS 26.5.2, Node 24.22.1): - `npm run check:fast` — passed - `npm run docs:check` — passed - `npm run test:local --` all `src/adb/*.test.ts`, `ExecFileOutput`, assetutil (now `main`'s) — 9 files, 309 passed, 1 skipped (Windows-only skip) - `npm run test:focused --` `adbCli`, Darwin token reader and DMG reader boundary files (the latter two now `main`'s) — 3 files, 121 passed - Earlier, at `8752d6e79`: `src/adb/AdbProvider.ts` reverted — the probe-capacity test fails; at `454f0177f`: `src/adb/AdbConfiguration.ts` reverted to `main` — the two new CLI tests fail; `host_capacity` mapping reverted — the capacity readiness test fails - Built-CLI E2E above ran at `454f0177f` with adb 37.0.1; the adb classification is unchanged since, so it was not rerun. Not run: full `npm run check:pr`, Linux, Windows. ## Compatibility, safety, and release - Breaking changes or migration steps: none. Same readiness error code; refusal text is more specific. Host capacity from adb device operations is now a retryable resource constraint instead of a protocol or unavailable error. - Real Hopper/Ghidra, browser, or OS coverage: macOS only, with a real adb client. - Package or release metadata impact: none. - Security, privacy, process, or containment review: the message echoes the caller-selected path, as the existing missing-path refusal already does. No new process is started. ## Author readiness Human owner: @chordpli - [x] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work. - [x] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [x] I declare this PR ready for initial maintainer review. ## Before merge - [ ] This PR addresses a concrete problem or an agreed enhancement. - [ ] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [ ] The PR has one focused outcome and the title follows `type(scope): outcome`. - [ ] Related issue is linked, or the reason for not linking one is stated above. - [ ] Tests cover changed observable behavior and meaningful failure paths. - [ ] Owning docs, contracts, and generated metadata are updated where needed. - [ ] User-visible CLI/MCP changes include representative output. - [ ] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#7194
No description provided.