[PR #7180] [MERGED] fix(session): report a missing Linux ps as provider unavailability in status #7698

Closed
opened 2026-11-03 13:39:44 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/7180
Author: @chordpli
Created: 10/25/2026
Status: ✅ Merged
Merged: 10/26/2026
Merged by: @gensokyo-dev

Base: main ← Head: fix/session-status-owned-process-prerequisite


📝 Commits (4)

  • feb79c0 fix(session): report a missing Linux ps as provider unavailability in status
  • a8e4244 refactor(session): trim redundant comments on the ps prerequisite fix
  • daeaa31 fix(session): keep status when any readiness probe meets a missing Linux ps
  • ec57e83 fix(session): keep the ps refusal owner when opening a binary

📊 Changes

10 files changed (+298 additions, -21 deletions)

View changed files

📝 docs/installation.md (+7 -0)
📝 src/android/JadxConfiguration.ts (+3 -0)
📝 src/apktool/ApktoolProvider.test.ts (+37 -0)
📝 src/apktool/ApktoolProvider.ts (+3 -0)
📝 src/application/binary/AnalysisProviderRegistry.test.ts (+51 -0)
📝 src/application/binary/AnalysisProviderRegistry.ts (+25 -12)
➕ src/application/ownedProcessAvailability.ts (+43 -0)
📝 src/process/ProcessOwnershipObservation.ts (+12 -7)
📝 tests/boundary/android/jadxConfiguration.test.ts (+41 -1)
📝 tests/boundary/mcp/sessionMcp.test.ts (+76 -1)

📄 Description

Summary

Fixes #7164.

On a Linux host without a procps-compatible ps on REA's PATH, binary_session {} failed as a whole with capability_unavailable (operation inspect_apktool_client, "spawn ps ENOENT") and returned no provider status at all. The status call now returns. The Apktool and JADX tool groups report available: false, reason provider_unavailable, with a remediation that names the missing ps. A configured Ghidra candidate, whose readiness check runs an owned Java probe, reports runtime_unavailable with the same reason. Every other provider keeps its own status.

Problem and expected behavior

Trigger: rea mcp over stdio with PATH lacking ps, then binary_session {} (the session.mjs from the issue). Observed on main 1173c0a9d: isError: true, the Linux owned-process refusal escapes the availability probe and the whole discovery call fails, even though Apktool is not installed.

Violated invariant: inspectAvailability is a bounded host observation that answers with a ProviderAvailability; the discovery call aggregates these answers and must not lose every provider's status because one optional probe met a host prerequisite it cannot satisfy.

The same shape applies to the JADX probe when REA_JADX_MCP_JAR and JAVA_HOME are configured, and to the deep-provider registry when GHIDRA_INSTALL_DIR is set: the Ghidra Java probe raises the refusal during readiness and the registry fails the whole status call (both reproduced below). The adb probe does not launch an owned child and was not affected.

Change and scope

  • Producer (src/process/ProcessOwnershipObservation.ts): the Linux prepare refusal is now a dedicated LinuxOwnedProcessPrerequisiteError (a subclass of AnalysisCapabilityUnavailableError with the same tag, provider id, operation, reason and user message). Execution-path projections are unchanged.
  • Shared projection (src/application/ownedProcessAvailability.ts): ownedProcessPrerequisiteAvailability recognizes that class, following capability errors that provider adapters re-attribute while keeping the original as cause (Ghidra does this), and returns { status: "unavailable", code: "runtime_unavailable", reason: <refusal reason + install guidance>, diagnostics: { stage: "ownership_preparation", ... } }. A refusal that carries incomplete cleanup is not projected.
  • Consumers: the Apktool and JADX availability probes, and the registry's candidate readiness step (AnalysisProviderRegistry) when it serves binary_session, return that availability instead of rethrowing. The registry keeps the provider's declared capabilities for the unavailable candidate. For open_binary the registry rethrows the refusal, so selection keeps the process-ownership owner and message. Execution paths (inspect_apktool_client, inspect_android_package, deep-provider opens, ...) still refuse with capability_unavailable; only discovery changes.
  • Docs: docs/installation.md "Linux owned-process prerequisite" states the discovery behavior.

Not included: a generic guard that swallows any probe rejection. Only this one host-prerequisite refusal is projected; unexpected probe errors, cancellation and incomplete cleanup still fail the call as before. The macOS helper-compilation failure path (ProviderAdapterError, stage ownership_preparation) is a different failure and is unchanged.

Contract and boundary impact

  • Semantic owner and earliest changed stage: the Linux ownership prepare refusal (process layer) owns the fact; the Apktool/JADX probes and the registry readiness step consume it through one projection. The string-based recognition from the first revision of this PR was removed. Session status aggregation and inventory mapping are unchanged.
  • Contribution scope fit and linked maintainer decision: ordinary fix within existing boundaries; not applicable.
  • CLI and MCP/tool-catalog contract: no schema change. binary_session output gains no fields; tool_availability groups for Apktool/JADX carry the ps reason as remediation on affected hosts.
  • Provider, bridge, target-format, or platform compatibility: Linux only in production (the prepare branch is Linux-specific); tests exercise the same path on macOS/Linux through the host prepare hook.
  • Evidence, artifact, provenance, or reconstruction contract: none.
  • Target selection, authority, upstream behavior, and cleanup details: not applicable; no child is launched when the prerequisite is missing.
  • Generated metadata, package, or installation impact: none.

Expected effects:

  • 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.

Evidence and regression coverage

  • Tests added or updated:
    • src/apktool/ApktoolProvider.test.ts: the prerequisite refusal becomes runtime_unavailable availability; inspect_apktool_client execution still refuses with capability_unavailable and provider_id: process-ownership.
    • tests/boundary/android/jadxConfiguration.test.ts: same for inspectJadxAvailability with a fake JDK and a valid engine JAR; validateJadxConfiguration still rejects.
    • src/application/binary/AnalysisProviderRegistry.test.ts: a candidate whose readiness rejects with a re-attributed capability error caused by the refusal becomes runtime_unavailable while another candidate stays available; explicit select for open_binary still returns the process-ownership refusal.
    • tests/boundary/mcp/sessionMcp.test.ts: a real ApktoolProvider behind createServer whose host prepare raises the refusal; binary_session returns status, the Apktool group is provider_unavailable with the ps remediation, and the adb group stays available. On the base revision this call returns isError: true.
  • Base reproduction or other evidence: Linux container E2E against main and the final head, six cases including Ghidra and two execution-path checks. See the table and details blocks below.
  • User-visible CLI/MCP output: in the details block (after).
  • Remaining proof gaps: no real Apktool, JADX or Ghidra installation on a ps-less Linux host was exercised; the JADX case uses a fake java script and the Ghidra case an empty install directory, both of which reach the owned Java probe (and so the prerequisite) first. Other deep providers whose readiness might launch an owned child are covered by the registry projection but were not individually exercised. Windows and macOS production paths are untouched.

E2E on the final head (ec57e834d) and on main (1173c0a9d), both built in the same Linux arm64 container (node:24-bookworm, Node 24.22.1). The script starts scripts/rea.mjs mcp over stdio with only HOME and the given PATH, calls binary_session {}, in cases 4 and 5 also runs inspect_apktool_client, and in case 6 runs only open_binary {path: "/usr/bin/env", provider_id: "ghidra"}. In the output, <ps-refusal> abbreviates "Linux process ownership inspection requires a procps-compatible ps on REA's PATH before launching a child:".

Case main this PR
1. PATH="" (issue repro) whole call fails status returned; Apktool provider_unavailable with the ps reason
2. PATH="" + JADX configured whole call fails status returned; JADX and Apktool provider_unavailable with the ps reason
3. PATH="" + GHIDRA_INSTALL_DIR whole call fails status returned; Ghidra candidate runtime_unavailable with the ps reason
4. PATH="", then run inspect_apktool_client refuses with capability_unavailable unchanged: same code, provider and remediation.action
5. ps on PATH (control) Apktool available, run succeeds unchanged
6. PATH="" + GHIDRA_INSTALL_DIR, open_binary with provider_id: "ghidra" refuses with the ps message unchanged
main 1173c0a9d
===== BEFORE (main 1173c0a9d) on aarch64, node v24.22.1, ps in image: /usr/bin/ps
-- 1. PATH="" (issue repro)
binary_session isError: true
  error: capability_unavailable | provider: process-ownership | operation: inspect_apktool_client | <ps-refusal> spawn ps ENOENT
-- 2. PATH="" + JADX configured
binary_session isError: true
  error: capability_unavailable | provider: process-ownership | operation: inspect_apktool_client | <ps-refusal> spawn ps ENOENT
-- 3. PATH="" + GHIDRA_INSTALL_DIR
binary_session isError: true
  error: capability_unavailable | provider: process-ownership | operation: inspect_apktool_client | <ps-refusal> spawn ps ENOENT
-- 4. PATH="" + Apktool configured, then run inspect_apktool_client
binary_session isError: true
  error: capability_unavailable | provider: process-ownership | operation: inspect_apktool_client | <ps-refusal> spawn ps ENOENT
inspect_apktool_client isError: true | capability_unavailable | provider=process-ownership | action=<ps-refusal> spawn ps ENOENT
-- 5. PATH=<only ps> + Apktool configured (control: prerequisite present)
binary_session isError: false
  inspect_apktool_client: available=true reason=available | 
  inspect_android_package: available=false reason=provider_missing | Set REA_JADX_MCP_JAR to the absolute path of a caller-supplied jadx-headless-mcp 0.7.1 JAR. REA does not download or install it.
  inspect_adb_client: available=false reason=provider_unavailable | The selected adb binary was not found: adb
  candidate ghidra: unavailable code=not_configured | Set GHIDRA_INSTALL_DIR to an extracted Ghidra 12.1.x release directory.
inspect_apktool_client isError: false 
-- 6. PATH="" + GHIDRA_INSTALL_DIR, open_binary with provider_id=ghidra
open_binary(provider_id=ghidra) isError: true | execution_failure | provider=rea | operation=open_binary | <ps-refusal> spawn ps ENOENT
this PR ec57e834d
===== AFTER (this PR ec57e834d) on aarch64, node v24.22.1, ps in image: /usr/bin/ps
-- 1. PATH="" (issue repro)
binary_session isError: false
  inspect_apktool_client: available=false reason=provider_unavailable | <ps-refusal> spawn ps ENOENT. Install your distribution's procps or procps-ng package, or put a procps-compatible ps on REA's PATH, then retry.
  inspect_android_package: available=false reason=provider_missing | Set REA_JADX_MCP_JAR to the absolute path of a caller-supplied jadx-headless-mcp 0.7.1 JAR. REA does not download or install it.
  inspect_adb_client: available=false reason=provider_unavailable | The selected adb binary was not found: adb
  candidate ghidra: unavailable code=not_configured | Set GHIDRA_INSTALL_DIR to an extracted Ghidra 12.1.x release directory.
-- 2. PATH="" + JADX configured
binary_session isError: false
  inspect_apktool_client: available=false reason=provider_unavailable | <ps-refusal> spawn ps ENOENT. Install your distribution's procps or procps-ng package, or put a procps-compatible ps on REA's PATH, then retry.
  inspect_android_package: available=false reason=provider_unavailable | <ps-refusal> spawn ps ENOENT. Install your distribution's procps or procps-ng package, or put a procps-compatible ps on REA's PATH, then retry.
  inspect_adb_client: available=false reason=provider_unavailable | The selected adb binary was not found: adb
  candidate ghidra: unavailable code=not_configured | Set GHIDRA_INSTALL_DIR to an extracted Ghidra 12.1.x release directory.
-- 3. PATH="" + GHIDRA_INSTALL_DIR
binary_session isError: false
  inspect_apktool_client: available=false reason=provider_unavailable | <ps-refusal> spawn ps ENOENT. Install your distribution's procps or procps-ng package, or put a procps-compatible ps on REA's PATH, then retry.
  inspect_android_package: available=false reason=provider_missing | Set REA_JADX_MCP_JAR to the absolute path of a caller-supplied jadx-headless-mcp 0.7.1 JAR. REA does not download or install it.
  inspect_adb_client: available=false reason=provider_unavailable | The selected adb binary was not found: adb
  candidate ghidra: unavailable code=runtime_unavailable | <ps-refusal> spawn ps ENOENT. Install your distribution's procps or procps-ng package, or put a procps-compatible ps on REA's PATH, then retry.
-- 4. PATH="" + Apktool configured, then run inspect_apktool_client
binary_session isError: false
  inspect_apktool_client: available=false reason=provider_unavailable | <ps-refusal> spawn ps ENOENT. Install your distribution's procps or procps-ng package, or put a procps-compatible ps on REA's PATH, then retry.
  inspect_android_package: available=false reason=provider_missing | Set REA_JADX_MCP_JAR to the absolute path of a caller-supplied jadx-headless-mcp 0.7.1 JAR. REA does not download or install it.
  inspect_adb_client: available=false reason=provider_unavailable | The selected adb binary was not found: adb
  candidate ghidra: unavailable code=not_configured | Set GHIDRA_INSTALL_DIR to an extracted Ghidra 12.1.x release directory.
inspect_apktool_client isError: true | capability_unavailable | provider=process-ownership | action=<ps-refusal> spawn ps ENOENT
-- 5. PATH=<only ps> + Apktool configured (control: prerequisite present)
binary_session isError: false
  inspect_apktool_client: available=true reason=available | 
  inspect_android_package: available=false reason=provider_missing | Set REA_JADX_MCP_JAR to the absolute path of a caller-supplied jadx-headless-mcp 0.7.1 JAR. REA does not download or install it.
  inspect_adb_client: available=false reason=provider_unavailable | The selected adb binary was not found: adb
  candidate ghidra: unavailable code=not_configured | Set GHIDRA_INSTALL_DIR to an extracted Ghidra 12.1.x release directory.
inspect_apktool_client isError: false 
-- 6. PATH="" + GHIDRA_INSTALL_DIR, open_binary with provider_id=ghidra
open_binary(provider_id=ghidra) isError: true | execution_failure | provider=rea | operation=open_binary | <ps-refusal> spawn ps ENOENT

Boundary regressions covered:

  • Missing, empty, and unknown values: a provider whose readiness cannot be established because ps is missing is reported as runtime_unavailable with the ps reason, instead of failing the whole status; other providers keep their own availability (registry test, sessionMcp.test.ts, E2E cases 1-3, control case 5).
  • Public success and error delivery: binary_session through a real MCP server in sessionMcp.test.ts and the stdio E2E above; the execution refusal keeps its code, provider and remediation.action (inspect_apktool_client case 4, open_binary case 6). Gap: the CLI was not run separately.

Lifecycle regressions: not applicable. The refusal happens before any child starts, so no process or cleanup stage is involved.

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.

Validation performed

  • npm run test:local -- src/application/binary/AnalysisProviderRegistry.test.ts src/apktool/ApktoolProvider.test.ts — 98 passed.
  • npm run test:focused -- tests/boundary/android/jadxConfiguration.test.ts tests/boundary/mcp/sessionMcp.test.ts src/application/binary/BinarySessionStatus.test.ts — 35 passed.
  • npm run check:fast — typecheck and lint passed.
  • npm run docs:check — passed.
  • Linux container E2E on main 1173c0a9d and the final head ec57e834d — all six cases as in the table: cases 1-3 fail as a whole on main and return status on this PR; cases 4-6 are identical.
  • Case 6 on the previous head daeaa31b7 returned the generic provider_unavailable "The selected analysis provider is unavailable" without the ps reason. ec57e834d fixes that, and the new registry assertion fails without the fix.
  • At ec57e834d: npm run test:focused -- registry, Apktool, JADX configuration and session MCP files — 126 passed; npm run check:fast and npm run docs:check — passed.
  • An earlier focused run had the pre-existing "joins a cancelled Java readiness probe" JADX test time out while a Docker build ran on the same host; it passed when rerun alone and in every later run.

Compatibility, safety, and release

  • Breaking changes or migration steps: none. Execution errors keep their code, reason and remediation.action; only discovery output changes.
  • Real Hopper/Ghidra, browser, or OS coverage: Linux arm64 container (Docker Desktop on macOS) for the MCP stdio run; macOS arm64 for the test suite.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: no new process launch; the remediation text contains no host paths beyond the already-reported ps lookup failure.

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/7180 **Author:** [@chordpli](https://github.com/chordpli) **Created:** 10/25/2026 **Status:** ✅ Merged **Merged:** 10/26/2026 **Merged by:** [@gensokyo-dev](https://github.com/gensokyo-dev) **Base:** `main` ← **Head:** `fix/session-status-owned-process-prerequisite` --- ### 📝 Commits (4) - [`feb79c0`](https://github.com/morluto/rea/commit/feb79c0670d188fcdfc4ce6e88797d802dcc6c8f) fix(session): report a missing Linux ps as provider unavailability in status - [`a8e4244`](https://github.com/morluto/rea/commit/a8e424412244ef53a622b00883a7c69484c70cf9) refactor(session): trim redundant comments on the ps prerequisite fix - [`daeaa31`](https://github.com/morluto/rea/commit/daeaa31b7c22b94459d64dc781c7e34104cb39ba) fix(session): keep status when any readiness probe meets a missing Linux ps - [`ec57e83`](https://github.com/morluto/rea/commit/ec57e834df0074434710187d1fc536681fdc8954) fix(session): keep the ps refusal owner when opening a binary ### 📊 Changes **10 files changed** (+298 additions, -21 deletions) <details> <summary>View changed files</summary> 📝 `docs/installation.md` (+7 -0) 📝 `src/android/JadxConfiguration.ts` (+3 -0) 📝 `src/apktool/ApktoolProvider.test.ts` (+37 -0) 📝 `src/apktool/ApktoolProvider.ts` (+3 -0) 📝 `src/application/binary/AnalysisProviderRegistry.test.ts` (+51 -0) 📝 `src/application/binary/AnalysisProviderRegistry.ts` (+25 -12) ➕ `src/application/ownedProcessAvailability.ts` (+43 -0) 📝 `src/process/ProcessOwnershipObservation.ts` (+12 -7) 📝 `tests/boundary/android/jadxConfiguration.test.ts` (+41 -1) 📝 `tests/boundary/mcp/sessionMcp.test.ts` (+76 -1) </details> ### 📄 Description ## Summary Fixes #7164. On a Linux host without a procps-compatible `ps` on REA's `PATH`, `binary_session {}` failed as a whole with `capability_unavailable` (operation `inspect_apktool_client`, "spawn ps ENOENT") and returned no provider status at all. The status call now returns. The Apktool and JADX tool groups report `available: false`, reason `provider_unavailable`, with a remediation that names the missing `ps`. A configured Ghidra candidate, whose readiness check runs an owned Java probe, reports `runtime_unavailable` with the same reason. Every other provider keeps its own status. ## Problem and expected behavior Trigger: `rea mcp` over stdio with `PATH` lacking `ps`, then `binary_session {}` (the `session.mjs` from the issue). Observed on `main` `1173c0a9d`: `isError: true`, the Linux owned-process refusal escapes the availability probe and the whole discovery call fails, even though Apktool is not installed. Violated invariant: `inspectAvailability` is a bounded host observation that answers with a `ProviderAvailability`; the discovery call aggregates these answers and must not lose every provider's status because one optional probe met a host prerequisite it cannot satisfy. The same shape applies to the JADX probe when `REA_JADX_MCP_JAR` and `JAVA_HOME` are configured, and to the deep-provider registry when `GHIDRA_INSTALL_DIR` is set: the Ghidra Java probe raises the refusal during readiness and the registry fails the whole status call (both reproduced below). The adb probe does not launch an owned child and was not affected. ## Change and scope - **Producer** (`src/process/ProcessOwnershipObservation.ts`): the Linux `prepare` refusal is now a dedicated `LinuxOwnedProcessPrerequisiteError` (a subclass of `AnalysisCapabilityUnavailableError` with the same tag, provider id, operation, reason and user message). Execution-path projections are unchanged. - **Shared projection** (`src/application/ownedProcessAvailability.ts`): `ownedProcessPrerequisiteAvailability` recognizes that class, following capability errors that provider adapters re-attribute while keeping the original as `cause` (Ghidra does this), and returns `{ status: "unavailable", code: "runtime_unavailable", reason: <refusal reason + install guidance>, diagnostics: { stage: "ownership_preparation", ... } }`. A refusal that carries incomplete cleanup is not projected. - **Consumers**: the Apktool and JADX availability probes, and the registry's candidate readiness step (`AnalysisProviderRegistry`) when it serves `binary_session`, return that availability instead of rethrowing. The registry keeps the provider's declared capabilities for the unavailable candidate. For `open_binary` the registry rethrows the refusal, so selection keeps the `process-ownership` owner and message. Execution paths (`inspect_apktool_client`, `inspect_android_package`, deep-provider opens, ...) still refuse with `capability_unavailable`; only discovery changes. - Docs: `docs/installation.md` "Linux owned-process prerequisite" states the discovery behavior. Not included: a generic guard that swallows any probe rejection. Only this one host-prerequisite refusal is projected; unexpected probe errors, cancellation and incomplete cleanup still fail the call as before. The macOS helper-compilation failure path (`ProviderAdapterError`, stage `ownership_preparation`) is a different failure and is unchanged. ## Contract and boundary impact - Semantic owner and earliest changed stage: the Linux ownership `prepare` refusal (process layer) owns the fact; the Apktool/JADX probes and the registry readiness step consume it through one projection. The string-based recognition from the first revision of this PR was removed. Session status aggregation and inventory mapping are unchanged. - Contribution scope fit and linked maintainer decision: ordinary fix within existing boundaries; not applicable. - CLI and MCP/tool-catalog contract: no schema change. `binary_session` output gains no fields; `tool_availability` groups for Apktool/JADX carry the `ps` reason as `remediation` on affected hosts. - Provider, bridge, target-format, or platform compatibility: Linux only in production (the `prepare` branch is Linux-specific); tests exercise the same path on macOS/Linux through the host `prepare` hook. - Evidence, artifact, provenance, or reconstruction contract: none. - Target selection, authority, upstream behavior, and cleanup details: not applicable; no child is launched when the prerequisite is missing. - Generated metadata, package, or installation impact: none. Expected effects: - [x] Read existing files or captured data. - [x] Launch or attach to a process. <!-- the probes launch `apktool --version` / `java` only when the prerequisite holds, as before --> - [ ] 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. ## Evidence and regression coverage - Tests added or updated: - `src/apktool/ApktoolProvider.test.ts`: the prerequisite refusal becomes `runtime_unavailable` availability; `inspect_apktool_client` execution still refuses with `capability_unavailable` and `provider_id: process-ownership`. - `tests/boundary/android/jadxConfiguration.test.ts`: same for `inspectJadxAvailability` with a fake JDK and a valid engine JAR; `validateJadxConfiguration` still rejects. - `src/application/binary/AnalysisProviderRegistry.test.ts`: a candidate whose readiness rejects with a re-attributed capability error caused by the refusal becomes `runtime_unavailable` while another candidate stays `available`; explicit `select` for `open_binary` still returns the `process-ownership` refusal. - `tests/boundary/mcp/sessionMcp.test.ts`: a real `ApktoolProvider` behind `createServer` whose host `prepare` raises the refusal; `binary_session` returns status, the Apktool group is `provider_unavailable` with the `ps` remediation, and the adb group stays `available`. On the base revision this call returns `isError: true`. - Base reproduction or other evidence: Linux container E2E against `main` and the final head, six cases including Ghidra and two execution-path checks. See the table and details blocks below. - User-visible CLI/MCP output: in the details block (after). - Remaining proof gaps: no real Apktool, JADX or Ghidra installation on a `ps`-less Linux host was exercised; the JADX case uses a fake `java` script and the Ghidra case an empty install directory, both of which reach the owned Java probe (and so the prerequisite) first. Other deep providers whose readiness might launch an owned child are covered by the registry projection but were not individually exercised. Windows and macOS production paths are untouched. E2E on the final head (`ec57e834d`) and on `main` (`1173c0a9d`), both built in the same Linux arm64 container (`node:24-bookworm`, Node 24.22.1). The script starts `scripts/rea.mjs mcp` over stdio with only `HOME` and the given `PATH`, calls `binary_session {}`, in cases 4 and 5 also runs `inspect_apktool_client`, and in case 6 runs only `open_binary {path: "/usr/bin/env", provider_id: "ghidra"}`. In the output, `<ps-refusal>` abbreviates "Linux process ownership inspection requires a procps-compatible ps on REA's PATH before launching a child:". | Case | main | this PR | |---|---|---| | 1. `PATH=""` (issue repro) | whole call fails | status returned; Apktool `provider_unavailable` with the `ps` reason | | 2. `PATH=""` + JADX configured | whole call fails | status returned; JADX and Apktool `provider_unavailable` with the `ps` reason | | 3. `PATH=""` + `GHIDRA_INSTALL_DIR` | whole call fails | status returned; Ghidra candidate `runtime_unavailable` with the `ps` reason | | 4. `PATH=""`, then run `inspect_apktool_client` | refuses with `capability_unavailable` | unchanged: same code, provider and `remediation.action` | | 5. `ps` on `PATH` (control) | Apktool available, run succeeds | unchanged | | 6. `PATH=""` + `GHIDRA_INSTALL_DIR`, `open_binary` with `provider_id: "ghidra"` | refuses with the `ps` message | unchanged | <details> <summary>main 1173c0a9d</summary> ```text ===== BEFORE (main 1173c0a9d) on aarch64, node v24.22.1, ps in image: /usr/bin/ps -- 1. PATH="" (issue repro) binary_session isError: true error: capability_unavailable | provider: process-ownership | operation: inspect_apktool_client | <ps-refusal> spawn ps ENOENT -- 2. PATH="" + JADX configured binary_session isError: true error: capability_unavailable | provider: process-ownership | operation: inspect_apktool_client | <ps-refusal> spawn ps ENOENT -- 3. PATH="" + GHIDRA_INSTALL_DIR binary_session isError: true error: capability_unavailable | provider: process-ownership | operation: inspect_apktool_client | <ps-refusal> spawn ps ENOENT -- 4. PATH="" + Apktool configured, then run inspect_apktool_client binary_session isError: true error: capability_unavailable | provider: process-ownership | operation: inspect_apktool_client | <ps-refusal> spawn ps ENOENT inspect_apktool_client isError: true | capability_unavailable | provider=process-ownership | action=<ps-refusal> spawn ps ENOENT -- 5. PATH=<only ps> + Apktool configured (control: prerequisite present) binary_session isError: false inspect_apktool_client: available=true reason=available | inspect_android_package: available=false reason=provider_missing | Set REA_JADX_MCP_JAR to the absolute path of a caller-supplied jadx-headless-mcp 0.7.1 JAR. REA does not download or install it. inspect_adb_client: available=false reason=provider_unavailable | The selected adb binary was not found: adb candidate ghidra: unavailable code=not_configured | Set GHIDRA_INSTALL_DIR to an extracted Ghidra 12.1.x release directory. inspect_apktool_client isError: false -- 6. PATH="" + GHIDRA_INSTALL_DIR, open_binary with provider_id=ghidra open_binary(provider_id=ghidra) isError: true | execution_failure | provider=rea | operation=open_binary | <ps-refusal> spawn ps ENOENT ``` </details> <details> <summary>this PR ec57e834d</summary> ```text ===== AFTER (this PR ec57e834d) on aarch64, node v24.22.1, ps in image: /usr/bin/ps -- 1. PATH="" (issue repro) binary_session isError: false inspect_apktool_client: available=false reason=provider_unavailable | <ps-refusal> spawn ps ENOENT. Install your distribution's procps or procps-ng package, or put a procps-compatible ps on REA's PATH, then retry. inspect_android_package: available=false reason=provider_missing | Set REA_JADX_MCP_JAR to the absolute path of a caller-supplied jadx-headless-mcp 0.7.1 JAR. REA does not download or install it. inspect_adb_client: available=false reason=provider_unavailable | The selected adb binary was not found: adb candidate ghidra: unavailable code=not_configured | Set GHIDRA_INSTALL_DIR to an extracted Ghidra 12.1.x release directory. -- 2. PATH="" + JADX configured binary_session isError: false inspect_apktool_client: available=false reason=provider_unavailable | <ps-refusal> spawn ps ENOENT. Install your distribution's procps or procps-ng package, or put a procps-compatible ps on REA's PATH, then retry. inspect_android_package: available=false reason=provider_unavailable | <ps-refusal> spawn ps ENOENT. Install your distribution's procps or procps-ng package, or put a procps-compatible ps on REA's PATH, then retry. inspect_adb_client: available=false reason=provider_unavailable | The selected adb binary was not found: adb candidate ghidra: unavailable code=not_configured | Set GHIDRA_INSTALL_DIR to an extracted Ghidra 12.1.x release directory. -- 3. PATH="" + GHIDRA_INSTALL_DIR binary_session isError: false inspect_apktool_client: available=false reason=provider_unavailable | <ps-refusal> spawn ps ENOENT. Install your distribution's procps or procps-ng package, or put a procps-compatible ps on REA's PATH, then retry. inspect_android_package: available=false reason=provider_missing | Set REA_JADX_MCP_JAR to the absolute path of a caller-supplied jadx-headless-mcp 0.7.1 JAR. REA does not download or install it. inspect_adb_client: available=false reason=provider_unavailable | The selected adb binary was not found: adb candidate ghidra: unavailable code=runtime_unavailable | <ps-refusal> spawn ps ENOENT. Install your distribution's procps or procps-ng package, or put a procps-compatible ps on REA's PATH, then retry. -- 4. PATH="" + Apktool configured, then run inspect_apktool_client binary_session isError: false inspect_apktool_client: available=false reason=provider_unavailable | <ps-refusal> spawn ps ENOENT. Install your distribution's procps or procps-ng package, or put a procps-compatible ps on REA's PATH, then retry. inspect_android_package: available=false reason=provider_missing | Set REA_JADX_MCP_JAR to the absolute path of a caller-supplied jadx-headless-mcp 0.7.1 JAR. REA does not download or install it. inspect_adb_client: available=false reason=provider_unavailable | The selected adb binary was not found: adb candidate ghidra: unavailable code=not_configured | Set GHIDRA_INSTALL_DIR to an extracted Ghidra 12.1.x release directory. inspect_apktool_client isError: true | capability_unavailable | provider=process-ownership | action=<ps-refusal> spawn ps ENOENT -- 5. PATH=<only ps> + Apktool configured (control: prerequisite present) binary_session isError: false inspect_apktool_client: available=true reason=available | inspect_android_package: available=false reason=provider_missing | Set REA_JADX_MCP_JAR to the absolute path of a caller-supplied jadx-headless-mcp 0.7.1 JAR. REA does not download or install it. inspect_adb_client: available=false reason=provider_unavailable | The selected adb binary was not found: adb candidate ghidra: unavailable code=not_configured | Set GHIDRA_INSTALL_DIR to an extracted Ghidra 12.1.x release directory. inspect_apktool_client isError: false -- 6. PATH="" + GHIDRA_INSTALL_DIR, open_binary with provider_id=ghidra open_binary(provider_id=ghidra) isError: true | execution_failure | provider=rea | operation=open_binary | <ps-refusal> spawn ps ENOENT ``` </details> Boundary regressions covered: - Missing, empty, and unknown values: a provider whose readiness cannot be established because `ps` is missing is reported as `runtime_unavailable` with the `ps` reason, instead of failing the whole status; other providers keep their own availability (registry test, `sessionMcp.test.ts`, E2E cases 1-3, control case 5). - Public success and error delivery: `binary_session` through a real MCP server in `sessionMcp.test.ts` and the stdio E2E above; the execution refusal keeps its code, provider and `remediation.action` (`inspect_apktool_client` case 4, `open_binary` case 6). Gap: the CLI was not run separately. Lifecycle regressions: not applicable. The refusal happens before any child starts, so no process or cleanup stage is involved. For evidence-bearing changes: - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed - `npm run test:local -- src/application/binary/AnalysisProviderRegistry.test.ts src/apktool/ApktoolProvider.test.ts` — 98 passed. - `npm run test:focused -- tests/boundary/android/jadxConfiguration.test.ts tests/boundary/mcp/sessionMcp.test.ts src/application/binary/BinarySessionStatus.test.ts` — 35 passed. - `npm run check:fast` — typecheck and lint passed. - `npm run docs:check` — passed. - Linux container E2E on `main` `1173c0a9d` and the final head `ec57e834d` — all six cases as in the table: cases 1-3 fail as a whole on main and return status on this PR; cases 4-6 are identical. - Case 6 on the previous head `daeaa31b7` returned the generic `provider_unavailable` "The selected analysis provider is unavailable" without the `ps` reason. `ec57e834d` fixes that, and the new registry assertion fails without the fix. - At `ec57e834d`: `npm run test:focused --` registry, Apktool, JADX configuration and session MCP files — 126 passed; `npm run check:fast` and `npm run docs:check` — passed. - An earlier focused run had the pre-existing "joins a cancelled Java readiness probe" JADX test time out while a Docker build ran on the same host; it passed when rerun alone and in every later run. ## Compatibility, safety, and release - Breaking changes or migration steps: none. Execution errors keep their code, reason and `remediation.action`; only discovery output changes. - Real Hopper/Ghidra, browser, or OS coverage: Linux arm64 container (Docker Desktop on macOS) for the MCP stdio run; macOS arm64 for the test suite. - Package or release metadata impact: none. - Security, privacy, process, or containment review: no new process launch; the remediation text contains no host paths beyond the already-reported `ps` lookup failure. ## 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>
bsakaguchi 2026-11-03 13:39:44 +00:00
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#7698
No description provided.