Skip to content

fix(session): report a missing Linux ps as provider unavailability in status - #7180

Merged
gensokyo-dev merged 4 commits into
morluto:mainfrom
chordpli:fix/session-status-owned-process-prerequisite
Oct 26, 2026
Merged

gensokyo-dev merged 4 commits into
morluto:mainfrom
chordpli:fix/session-status-owned-process-prerequisite

Conversation

@chordpli

@chordpli chordpli commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

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 1173c0a
===== 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 ec57e83
===== 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.

… status

On Linux without a procps-compatible ps on REA's PATH, the Apktool and JADX
availability probes rethrew the owned-process prerequisite refusal, so
binary_session failed as a whole and returned no provider status.

Build the Linux refusal in one owner with a remediation, recognize it from the
two probes, and project it as runtime_unavailable availability so the status
call returns and the affected tool groups report provider_unavailable with
the ps reason. Execution paths keep refusing with capability_unavailable.

Fixes morluto#7164
@greptile-apps

greptile-apps Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no new blocking issue was found.

Summary

Missing Linux ps now makes affected providers report unavailable instead of failing all of binary_session.

  • A missing Linux ps no longer erases every provider's status.

Source checks found no new actionable issues. Tests were not run during this review. Whether another PR supersedes this one could not be verified because GitHub access was unavailable.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Linux ownership preparation] --> B[Missing or incompatible ps]
  B --> C[Dedicated prerequisite error]
  C --> D{Caller}
  D -->|binary_session readiness| E[Report affected provider unavailable]
  E --> F[Keep other provider statuses]
  D -->|open_binary selection| G[Preserve execution refusal]
Loading

Reviews (3) · Last reviewed commit: "fix(session): keep the ps refusal owner ..." · Reviewed by Greptile

Comment thread docs/installation.md Resolved
…nux ps

The Ghidra Java probe raised the same owned-process refusal during
binary_session readiness, and the registry failed the whole status call.
Project the refusal in the registry's candidate readiness step as well.

Recognize the refusal by a dedicated LinuxOwnedProcessPrerequisiteError,
following re-attributed capability errors through their cause, instead of
matching provider and operation strings. Keep the install guidance in the
availability reason only, so execution refusals keep their prior
remediation action.
@chordpli

Copy link
Copy Markdown
Contributor Author

Re-ran the Linux container E2E on the final head daeaa31b7 after the review follow-up (Ghidra readiness path, dedicated error class, remediation kept off the shared refusal). Same container and Node for main 1173c0a9d and this PR:

Case main this PR
PATH="" (issue repro) whole call fails status returned; Apktool provider_unavailable with the ps reason
PATH="" + JADX configured whole call fails status returned; JADX and Apktool provider_unavailable
PATH="" + GHIDRA_INSTALL_DIR whole call fails status returned; Ghidra candidate runtime_unavailable
PATH="", run inspect_apktool_client capability_unavailable unchanged, same remediation.action
ps on PATH (control) available, run succeeds unchanged

Full output is in the PR body. Tests: registry/apktool 98 passed, JADX/session/status boundary 35 passed, check:fast and docs:check passed.

@chordpli
chordpli marked this pull request as ready for review October 26, 2026 00:46
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-26T01:19:01.437647Z ec57e83 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: daeaa31b7c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Only binary_session projects a missing Linux ps into per-provider
unavailability. open_binary shares candidate inspection, so it now rethrows
the process-ownership refusal as before. The installation guide also notes
that earlier configuration failures keep their own reason.
@greptile-apps

greptile-apps Bot commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

Want your agent to iterate on Greptile's feedback? Try greploops.

@gensokyo-dev

Copy link
Copy Markdown
Collaborator

Independent validation of ec57e834df0074434710187d1fc536681fdc8954 integrated onto current main e3066c1705316b53b08f84814ae929eb6563e511 in an isolated worktree:

  • Reproduced through the actual packaged stdio MCP on Linux, Node 24.22.1. With a private HOME and empty PATH, main binary_session {} fails as a whole with capability_unavailable / inspect_apktool_client. Providing only a symlink to the system procps ps restores status.
  • With this patch and empty PATH, the same call succeeds and Apktool reports the prerequisite and procps repair. With ps present, tool availability and candidate statuses equal main. Direct inspect_apktool_client with no ps retains exactly the original structured refusal.
  • Focused Apktool/registry/JADX/MCP suite: 126 passed, one existing full MCP workflow hit its 30-second timeout under concurrent bounded validation. Isolated rerun of that exact test passes on both unchanged main (27.4s test time) and the integrated patch; no timeout or test edits. Every selected case has passed, while the initial timeout is retained as a failed attempt.
  • check:fast, docs:check, and diff whitespace checks pass. Reviewed the typed producing error, provider cause projection, status-only registry mapping, and execution refusal preservation; no further changes needed.
  • Two-CPU affinity throughout. Real no-ps MCP: main 3.105s wall / 3.477s CPU / 321.49 MiB sampled family RSS; patched 4.646s wall / 3.383s CPU / 332.21 MiB. Uncontrolled machine contention/cache, one sample per condition; no speedup claim. Successful status response approximately 34 KB, same scale as normal status. Token count unmeasured. RSS is 100ms /proc family sampling and can double-count shared pages or miss short peaks.
  • Existing PR CI is green (all eight boundary shards and required checks); current-head Codex review has no findings and previous threads are resolved. No actual Android/Ghidra engine execution claimed.

Merging the existing contributor PR preserves its authorship and avoids a duplicate implementation.

@gensokyo-dev
gensokyo-dev merged commit 0211a07 into morluto:main Oct 26, 2026
42 of 45 checks passed
@github-actions github-actions Bot mentioned this pull request Oct 26, 2026
3 tasks done
@chordpli
chordpli deleted the fix/session-status-owned-process-prerequisite branch October 26, 2026 13:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] binary_session fails entirely on Linux when ps is not on REA's PATH

2 participants