[PR #3573] fix(android): classify a pre-handshake JADX heap shortage as a resource limit #5831

Open
opened 2026-10-31 01:09:46 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/3573
Author: @Antruly
Created: 10/14/2026
Status: 🔄 Open

Base: main ← Head: fix/jadx-heap-exhaustion


📝 Commits (1)

  • df5a484 fix(android): classify a pre-handshake JADX heap shortage as a resource limit

📊 Changes

12 files changed (+266 additions, -3 deletions)

View changed files

📝 docs/android-analysis.md (+14 -1)
📝 docs/user-visible-errors.csv (+1 -0)
📝 src/android/JadxDoctor.ts (+25 -1)
📝 src/android/JadxFailure.ts (+23 -0)
📝 src/android/JadxProtocol.ts (+21 -0)
📝 src/android/JadxRelease.ts (+13 -0)
📝 src/doctorRuntime.test.ts (+29 -1)
📝 src/domain/analysisErrorProjection.test.ts (+31 -0)
📝 tests/boundary/android/jadxDoctor.test.ts (+39 -0)
📝 tests/boundary/android/jadxProtocol.test.ts (+25 -0)
📝 tests/boundary/process/jadxIntegration.test.ts (+37 -0)
📝 tests/fixtures/android/jadx-mcp.mjs (+8 -0)

📄 Description

Summary

A JADX worker heap too small for REA's own bridge stops the JVM before the protocol handshake. Its retained stderr is then the only evidence of why it stopped, and that failure was reported as capability_unavailable with configuration advice, so a caller could not tell a resource limit from a missing JDK or an unreadable archive.

After this change the same failure reports resource_constraint with details.resource: "memory". The message names the heap exhaustion, the remediation names REA_JADX_HEAP_MIB and its precedence over the JVM option variables, and details.reported_limits.observed_max_heap_bytes is null because the bridge never answered, so no runtime fact measured a heap. Any other launcher stop keeps the existing reply. rea doctor --provider jadx also states the heap REA requests.

Relates to #3517.

Scope after main 6.7.0

This branch was reduced and rebased onto main @ 95c8f57a (6.7.0), because that release already carries most of what the earlier revision did. 3fa972af ("fix(android): preserve typed JVM memory failures during loading", Fixes #3517) implements the bridge half from the other end of the boundary: a structured jvm-memory-failure contract in JadxProtocol.ts, the shared parseJadxFailure, and AnalysisResourceConstraintError with retryable: false for load_apk. JadxFailure.ts now owns capture, observation, and presentation of JADX failures.

What this branch keeps is the part 3fa972af leaves open. Its commit message lists "pre-handshake failures" among what remains unverified, and on main the startup branch of presentDiagnosticFailure still returns AnalysisCapabilityUnavailableError, whose only heap signal is a clause in the prose.

Removed by the reduction, because upstream now owns it: the bridge load_apk classification, the session-level heap observation, the text classification of bridge envelopes, and the inline jadxInputFailureSchema handling.

Change and scope

  • src/android/JadxFailure.ts: the startup branch of presentDiagnosticFailure classifies HotSpot's heap-exhaustion reasons in the launcher's retained stderr and returns AnalysisResourceConstraintError with resource: "memory", retryable: false, observed_max_heap_bytes: null, and the launcher output in captured_output. The existing startup message and every other start failure are unchanged.
  • src/android/JadxProtocol.ts: jadxHeapExhausted, matching only Java heap space and GC overhead limit exceeded. Metaspace, direct-buffer, and native-thread exhaustion are not heap shortages and keep the generic startup failure.
  • src/android/JadxRelease.ts: JADX_HEAP_EXHAUSTION_REMEDIATION, which states the precedence: REA passes the value as an explicit -Xmx, so it overrides JAVA_TOOL_OPTIONS and JDK_JAVA_OPTIONS, while _JAVA_OPTIONS overrides the command line.
  • src/android/JadxDoctor.ts: a heap check that states the requested heap, or that REA asks for none and the heap comes from the launching environment. It never fails the provider, because doctor reads configuration without starting the engine.
  • Tests: a protocol unit test for the two reasons and for the reasons that must not match; a projection test that a declared provider remediation outranks the generic memory advice; doctor tests at the provider and composition boundaries; and an integration test over the child-process MCP fixture's start-heap-exhausted mode.
  • docs/android-analysis.md and docs/user-visible-errors.csv.

Contract and boundary impact

  • Semantic owner and earliest changed stage: JadxFailure startup classification; JadxProtocol text predicate; JadxDoctor configuration reporting.
  • CLI and MCP/tool-catalog contract: for a pre-handshake heap shortage only, the failure changes from capability_unavailable to resource_constraint with details.resource: "memory". No operation, schema, or catalog entry changes.
  • Compatibility: no breaking change. A caller that treated every launcher stop as capability_unavailable now sees resource_constraint for this one cause.
  • Security, privacy, process, or containment review: none. No new process, file, or network behavior.

Validation performed

Run on df5a484e; local Node was 24.21.0 against the pinned 24.18.0.

  • npm run build:cached — 3 successful.
  • npm run check:fast — 2 successful (typecheck, lint).
  • npx oxfmt --check . — clean over 4077 files.
  • npm run docs:check — passed.
  • turbo run lint:built knip — 4 successful.
  • npm run test:focused -- tests/boundary/android/jadxProtocol.test.ts src/domain/analysisErrorProjection.test.ts — 63 passed.
  • npm run test:focused -- tests/boundary/android/jadxDoctor.test.ts src/doctorRuntime.test.ts — 31 passed.
  • npm run test:focused -- tests/boundary/process/jadxIntegration.test.ts — 49 passed, including upstream's heap-failure, other-memory-failure, and cleanup-retry cases.

Not run: npm run check:pr (the full test:run, python tests, jscpd, scan:todos) and real-provider verification. The earlier revision's real-JDK run is not repeated here: the code path moved from JadxProvider.executionError into JadxFailure.presentDiagnosticFailure, and this revision's evidence is the gates above.

The run approved on the previous head (#11494, attempt 2) was still queued when this branch was rewritten, so it does not describe df5a484e.

Compatibility, safety, and release

  • Breaking changes or migration steps: none.
  • Real Hopper/Ghidra, browser, or OS coverage: not applicable; Android/JADX only, and the change stays in failure presentation.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: none. Text-only classification of a launcher's own stderr plus the remediation selected for it; no new process, file, or network behavior. No secrets in the diff.

Author readiness

Human owner: @Antruly

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

The human owner completed the readiness declarations and the pre-merge
checklist above and declared the PR ready for initial maintainer review.


🔄 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/3573 **Author:** [@Antruly](https://github.com/Antruly) **Created:** 10/14/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `fix/jadx-heap-exhaustion` --- ### 📝 Commits (1) - [`df5a484`](https://github.com/morluto/rea/commit/df5a484e907383b5d9446dafbfa817f219482ab4) fix(android): classify a pre-handshake JADX heap shortage as a resource limit ### 📊 Changes **12 files changed** (+266 additions, -3 deletions) <details> <summary>View changed files</summary> 📝 `docs/android-analysis.md` (+14 -1) 📝 `docs/user-visible-errors.csv` (+1 -0) 📝 `src/android/JadxDoctor.ts` (+25 -1) 📝 `src/android/JadxFailure.ts` (+23 -0) 📝 `src/android/JadxProtocol.ts` (+21 -0) 📝 `src/android/JadxRelease.ts` (+13 -0) 📝 `src/doctorRuntime.test.ts` (+29 -1) 📝 `src/domain/analysisErrorProjection.test.ts` (+31 -0) 📝 `tests/boundary/android/jadxDoctor.test.ts` (+39 -0) 📝 `tests/boundary/android/jadxProtocol.test.ts` (+25 -0) 📝 `tests/boundary/process/jadxIntegration.test.ts` (+37 -0) 📝 `tests/fixtures/android/jadx-mcp.mjs` (+8 -0) </details> ### 📄 Description ## Summary A JADX worker heap too small for REA's own bridge stops the JVM before the protocol handshake. Its retained stderr is then the only evidence of why it stopped, and that failure was reported as `capability_unavailable` with configuration advice, so a caller could not tell a resource limit from a missing JDK or an unreadable archive. After this change the same failure reports `resource_constraint` with `details.resource: "memory"`. The message names the heap exhaustion, the remediation names `REA_JADX_HEAP_MIB` and its precedence over the JVM option variables, and `details.reported_limits.observed_max_heap_bytes` is `null` because the bridge never answered, so no runtime fact measured a heap. Any other launcher stop keeps the existing reply. `rea doctor --provider jadx` also states the heap REA requests. Relates to #3517. ## Scope after `main` 6.7.0 This branch was reduced and rebased onto `main` @ `95c8f57a` (6.7.0), because that release already carries most of what the earlier revision did. `3fa972af` ("fix(android): preserve typed JVM memory failures during loading", `Fixes #3517`) implements the bridge half from the other end of the boundary: a structured `jvm-memory-failure` contract in `JadxProtocol.ts`, the shared `parseJadxFailure`, and `AnalysisResourceConstraintError` with `retryable: false` for `load_apk`. `JadxFailure.ts` now owns capture, observation, and presentation of JADX failures. What this branch keeps is the part `3fa972af` leaves open. Its commit message lists "pre-handshake failures" among what remains unverified, and on `main` the `startup` branch of `presentDiagnosticFailure` still returns `AnalysisCapabilityUnavailableError`, whose only heap signal is a clause in the prose. Removed by the reduction, because upstream now owns it: the bridge `load_apk` classification, the session-level heap observation, the text classification of bridge envelopes, and the inline `jadxInputFailureSchema` handling. ## Change and scope - `src/android/JadxFailure.ts`: the `startup` branch of `presentDiagnosticFailure` classifies HotSpot's heap-exhaustion reasons in the launcher's retained stderr and returns `AnalysisResourceConstraintError` with `resource: "memory"`, `retryable: false`, `observed_max_heap_bytes: null`, and the launcher output in `captured_output`. The existing startup message and every other start failure are unchanged. - `src/android/JadxProtocol.ts`: `jadxHeapExhausted`, matching only `Java heap space` and `GC overhead limit exceeded`. Metaspace, direct-buffer, and native-thread exhaustion are not heap shortages and keep the generic startup failure. - `src/android/JadxRelease.ts`: `JADX_HEAP_EXHAUSTION_REMEDIATION`, which states the precedence: REA passes the value as an explicit `-Xmx`, so it overrides `JAVA_TOOL_OPTIONS` and `JDK_JAVA_OPTIONS`, while `_JAVA_OPTIONS` overrides the command line. - `src/android/JadxDoctor.ts`: a `heap` check that states the requested heap, or that REA asks for none and the heap comes from the launching environment. It never fails the provider, because doctor reads configuration without starting the engine. - Tests: a protocol unit test for the two reasons and for the reasons that must not match; a projection test that a declared provider remediation outranks the generic memory advice; doctor tests at the provider and composition boundaries; and an integration test over the child-process MCP fixture's `start-heap-exhausted` mode. - `docs/android-analysis.md` and `docs/user-visible-errors.csv`. ## Contract and boundary impact - Semantic owner and earliest changed stage: `JadxFailure` startup classification; `JadxProtocol` text predicate; `JadxDoctor` configuration reporting. - CLI and MCP/tool-catalog contract: for a pre-handshake heap shortage only, the failure changes from `capability_unavailable` to `resource_constraint` with `details.resource: "memory"`. No operation, schema, or catalog entry changes. - Compatibility: no breaking change. A caller that treated every launcher stop as `capability_unavailable` now sees `resource_constraint` for this one cause. - Security, privacy, process, or containment review: none. No new process, file, or network behavior. ## Validation performed Run on `df5a484e`; local Node was 24.21.0 against the pinned 24.18.0. - `npm run build:cached` — 3 successful. - `npm run check:fast` — 2 successful (typecheck, lint). - `npx oxfmt --check .` — clean over 4077 files. - `npm run docs:check` — passed. - `turbo run lint:built knip` — 4 successful. - `npm run test:focused -- tests/boundary/android/jadxProtocol.test.ts src/domain/analysisErrorProjection.test.ts` — 63 passed. - `npm run test:focused -- tests/boundary/android/jadxDoctor.test.ts src/doctorRuntime.test.ts` — 31 passed. - `npm run test:focused -- tests/boundary/process/jadxIntegration.test.ts` — 49 passed, including upstream's `heap-failure`, `other-memory-failure`, and cleanup-retry cases. Not run: `npm run check:pr` (the full `test:run`, python tests, `jscpd`, `scan:todos`) and real-provider verification. The earlier revision's real-JDK run is not repeated here: the code path moved from `JadxProvider.executionError` into `JadxFailure.presentDiagnosticFailure`, and this revision's evidence is the gates above. The run approved on the previous head (`#11494`, attempt 2) was still queued when this branch was rewritten, so it does not describe `df5a484e`. ## Compatibility, safety, and release - Breaking changes or migration steps: none. - Real Hopper/Ghidra, browser, or OS coverage: not applicable; Android/JADX only, and the change stays in failure presentation. - Package or release metadata impact: none. - Security, privacy, process, or containment review: none. Text-only classification of a launcher's own stderr plus the remediation selected for it; no new process, file, or network behavior. No secrets in the diff. ## Author readiness Human owner: @Antruly - [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 - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [x] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. The human owner completed the readiness declarations and the pre-merge checklist above and declared the PR ready for initial maintainer review. --- <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#5831
No description provided.