[GH-ISSUE #3340] [Bug] Apktool decode refusals report the launcher path instead of the selected APK #1207

Closed
opened 2026-10-15 18:32:01 +00:00 by bsakaguchi · 2 comments
Owner

Originally created by @coygeek on GitHub (Oct 14, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/3340

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

When the selected Apktool launcher refuses an APK, decode-android-resources reports the launcher executable as the unsupported target. Both the message and details.path lose the caller-selected APK identity, although the command retains the decode failure reason and captured decode output.

The public CLI reproduction exits with code 1 and reports fake-apktool as the target basename. The original error message also names fake-apktool after Unsupported target for decode_android_resources at. The APK path is absent from the returned error. The behavior also reproduced in a rerun against a ZIP input.

Reproduction

  1. Start from a clean checkout and build the baseline runtime. The reported reproduction used dependencies and compiled runtime that were already present; these commands prepare a clean checkout for the same invocation.
git clone https://github.com/morluto/rea.git
cd rea
git checkout 8dde6370d0809ad04cda3342d4e6f290f48aa063
npm ci
npm run build:cached
  1. From the repository root, create a local launcher fixture and an APK-shaped ZIP containing AndroidManifest.xml, then run the CLI. The fixture reports version 2.7.0 and refuses decoding with a controlled diagnostic. No real Apktool or Java installation is needed.
case_dir=$(mktemp -d)
mkdir "$case_dir/home"
cat > "$case_dir/fake-apktool" <<'SH'
#!/bin/sh
if [ "$1" = "--version" ]; then
  printf '2.7.0\n'
  exit 0
fi
printf 'brut.androlib.AndrolibException: Could not decode resource table\n' >&2
exit 1
SH
chmod +x "$case_dir/fake-apktool"
python3 - "$case_dir/sample.apk" <<'PYTHON'
import sys, zipfile
with zipfile.ZipFile(sys.argv[1], "w") as archive:
    archive.writestr("AndroidManifest.xml", "manifest")
PYTHON
HOME="$case_dir/home" TMPDIR="$case_dir" \
  REA_APKTOOL_COMMAND="$case_dir/fake-apktool" \
  node scripts/rea.mjs decode-android-resources "$case_dir/sample.apk" \
  --format json > "$case_dir/result.json"
printf 'CLI exit: %s\n' "$?"
python3 - "$case_dir/result.json" <<'PYTHON'
import json, pathlib, sys
result = json.load(open(sys.argv[1]))
print(json.dumps({
    "code": result["code"],
    "reported_target_name": pathlib.Path(result["details"]["path"]).name,
    "reason": result["details"]["reason"],
    "decode_exit_code": result["details"]["captured_output"]["exit_code"]
}, indent=2))
PYTHON
  1. Observe that the refusal identifies fake-apktool rather than sample.apk, while preserving the controlled decode diagnostic and captured exit code 1.

Expected behavior

The refusal should identify sample.apk as the selected unsupported target in its message and details.path, while preserving the launcher's diagnostic and captured decode output. The launcher is the producing tool, and the APK is the artifact supplied to the operation.

AGENTS.md:13, under Product and Authority, requires preserving caller-selected inputs and paths. src/domain/analysisErrorCore.ts:7 defines an unsupported target as the supplied artifact. docs/apktool-resource-analysis.md:72, under Resource and lifecycle limits, and docs/apktool-resource-analysis.md:83 specify that Apktool decode refusals preserve the reason as unsupported_target.

Impact and scope

Impact level: P2, supported. Decode failure evidence attributes the refusal to the provider executable rather than the selected artifact, so callers cannot recover the target identity from the error. The observed case is bounded to decode refusal output; successful decode results were not implicated.

Workaround status: available. The caller can retain the input path alongside the failed invocation and use that selection when interpreting the preserved diagnostic. This avoids relying on the incorrect target field and preserves the underlying failure reason.

REA version

6.4.0 (main at 8dde6370d0)

Runtime and platform

Node.js v26.10.0, macOS 27.0.1 arm64. The observed run used checkout dependencies and a compiled runtime that were already present. The launcher fixture does not require real Apktool or Java.

Target and artifact context

A generated ZIP named sample.apk containing AndroidManifest.xml with the literal content manifest. The controlled launcher ignores the APK contents and emits a decode refusal. The public CLI behavior also reproduced against a ZIP input; this establishes refusal projection without claiming a real Apktool engine test.

Interface

CLI

Earliest failure stage

Transport or result projection

Relevant output or evidence

CLI exit: 1
{
  "code": "unsupported_target",
  "reported_target_name": "fake-apktool",
  "reason": "brut.androlib.AndrolibException: Could not decode resource table",
  "decode_exit_code": 1
}

The original error message names fake-apktool after Unsupported target for decode_android_resources at; it omits the selected APK path. The CLI result is the primary violation evidence, and source inspection explains the path substitution. The failure was reproduced locally through the public CLI with the complete fixture above.

Typed outcome or recovery expected

Repeat the command. The error should remain an unsupported_target decode refusal with the same reason and captured exit code 1, but the reported target basename must be sample.apk, and its message must identify that APK. Repeat with a differently named APK to ensure the result follows the caller's selection rather than a fixed path.

Analysis or suspected cause

Source-derived reasoning: src/apktool/ApktoolProvider.ts:249 constructs the decode refusal using selection.command as its target path. Passing the selected launcher instead of the caller-selected APK explains why both the message and details.path identify fake-apktool. This reasoning is separate from the observed CLI output.

Additional context

  • #1945, closed as completed, concerns missing-launcher advice and relative launcher configuration escaping as UNKNOWN; this report concerns an admitted launcher refusing an APK and losing the artifact path.
  • #1952, closed as completed, concerns ADB configuration refusal messages; it does not concern Apktool decode artifact identity.
  • #2672, closed as completed, concerns CLI rejection of the documented string omission flag before provider dispatch; this invocation reaches the launcher and fails during decoding.
  • #1774, closed as completed, concerns Windows batch-launcher version-probe failure; this fixture passes version admission and then refuses decoding.
  • #3068, closed as completed, concerns incorrect Apktool availability in binary_session; this report concerns the target path in a decode refusal.
  • #2563, closed as completed, concerns locale discovery in successful decode output; this report concerns error output after failed decoding.
  • #1813 proposes split APK set identity and relationship evidence; this report preserves the identity of one selected APK in an existing operation's refusal.
  • #717 covers the Android integration roadmap; it does not report this specific decode-refusal path substitution.
Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3340 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Evidence, artifacts, or reconstruction ### Observed behavior When the selected Apktool launcher refuses an APK, `decode-android-resources` reports the launcher executable as the unsupported target. Both the message and `details.path` lose the caller-selected APK identity, although the command retains the decode failure reason and captured decode output. The public CLI reproduction exits with code 1 and reports `fake-apktool` as the target basename. The original error message also names `fake-apktool` after `Unsupported target for decode_android_resources at`. The APK path is absent from the returned error. The behavior also reproduced in a rerun against a ZIP input. ### Reproduction 1. Start from a clean checkout and build the baseline runtime. The reported reproduction used dependencies and compiled runtime that were already present; these commands prepare a clean checkout for the same invocation. ```sh git clone https://github.com/morluto/rea.git cd rea git checkout 8dde6370d0809ad04cda3342d4e6f290f48aa063 npm ci npm run build:cached ``` 2. From the repository root, create a local launcher fixture and an APK-shaped ZIP containing `AndroidManifest.xml`, then run the CLI. The fixture reports version `2.7.0` and refuses decoding with a controlled diagnostic. No real Apktool or Java installation is needed. ```sh case_dir=$(mktemp -d) mkdir "$case_dir/home" cat > "$case_dir/fake-apktool" <<'SH' #!/bin/sh if [ "$1" = "--version" ]; then printf '2.7.0\n' exit 0 fi printf 'brut.androlib.AndrolibException: Could not decode resource table\n' >&2 exit 1 SH chmod +x "$case_dir/fake-apktool" python3 - "$case_dir/sample.apk" <<'PYTHON' import sys, zipfile with zipfile.ZipFile(sys.argv[1], "w") as archive: archive.writestr("AndroidManifest.xml", "manifest") PYTHON HOME="$case_dir/home" TMPDIR="$case_dir" \ REA_APKTOOL_COMMAND="$case_dir/fake-apktool" \ node scripts/rea.mjs decode-android-resources "$case_dir/sample.apk" \ --format json > "$case_dir/result.json" printf 'CLI exit: %s\n' "$?" python3 - "$case_dir/result.json" <<'PYTHON' import json, pathlib, sys result = json.load(open(sys.argv[1])) print(json.dumps({ "code": result["code"], "reported_target_name": pathlib.Path(result["details"]["path"]).name, "reason": result["details"]["reason"], "decode_exit_code": result["details"]["captured_output"]["exit_code"] }, indent=2)) PYTHON ``` 3. Observe that the refusal identifies `fake-apktool` rather than `sample.apk`, while preserving the controlled decode diagnostic and captured exit code 1. ### Expected behavior The refusal should identify `sample.apk` as the selected unsupported target in its message and `details.path`, while preserving the launcher's diagnostic and captured decode output. The launcher is the producing tool, and the APK is the artifact supplied to the operation. `AGENTS.md:13`, under Product and Authority, requires preserving caller-selected inputs and paths. `src/domain/analysisErrorCore.ts:7` defines an unsupported target as the supplied artifact. `docs/apktool-resource-analysis.md:72`, under Resource and lifecycle limits, and `docs/apktool-resource-analysis.md:83` specify that Apktool decode refusals preserve the reason as `unsupported_target`. ### Impact and scope Impact level: P2, supported. Decode failure evidence attributes the refusal to the provider executable rather than the selected artifact, so callers cannot recover the target identity from the error. The observed case is bounded to decode refusal output; successful decode results were not implicated. Workaround status: available. The caller can retain the input path alongside the failed invocation and use that selection when interpreting the preserved diagnostic. This avoids relying on the incorrect target field and preserves the underlying failure reason. ### REA version 6.4.0 (main at 8dde6370d0809ad04cda3342d4e6f290f48aa063) ### Runtime and platform Node.js v26.10.0, macOS 27.0.1 arm64. The observed run used checkout dependencies and a compiled runtime that were already present. The launcher fixture does not require real Apktool or Java. ### Target and artifact context A generated ZIP named `sample.apk` containing `AndroidManifest.xml` with the literal content `manifest`. The controlled launcher ignores the APK contents and emits a decode refusal. The public CLI behavior also reproduced against a ZIP input; this establishes refusal projection without claiming a real Apktool engine test. ### Interface CLI ### Earliest failure stage Transport or result projection ### Relevant output or evidence ```text CLI exit: 1 { "code": "unsupported_target", "reported_target_name": "fake-apktool", "reason": "brut.androlib.AndrolibException: Could not decode resource table", "decode_exit_code": 1 } ``` The original error message names `fake-apktool` after `Unsupported target for decode_android_resources at`; it omits the selected APK path. The CLI result is the primary violation evidence, and source inspection explains the path substitution. The failure was reproduced locally through the public CLI with the complete fixture above. ### Typed outcome or recovery expected Repeat the command. The error should remain an `unsupported_target` decode refusal with the same reason and captured exit code 1, but the reported target basename must be `sample.apk`, and its message must identify that APK. Repeat with a differently named APK to ensure the result follows the caller's selection rather than a fixed path. ### Analysis or suspected cause Source-derived reasoning: `src/apktool/ApktoolProvider.ts:249` constructs the decode refusal using `selection.command` as its target path. Passing the selected launcher instead of the caller-selected APK explains why both the message and `details.path` identify `fake-apktool`. This reasoning is separate from the observed CLI output. ### Additional context - [#1945](https://github.com/morluto/rea/issues/1945), closed as completed, concerns missing-launcher advice and relative launcher configuration escaping as UNKNOWN; this report concerns an admitted launcher refusing an APK and losing the artifact path. - [#1952](https://github.com/morluto/rea/issues/1952), closed as completed, concerns ADB configuration refusal messages; it does not concern Apktool decode artifact identity. - [#2672](https://github.com/morluto/rea/issues/2672), closed as completed, concerns CLI rejection of the documented string omission flag before provider dispatch; this invocation reaches the launcher and fails during decoding. - [#1774](https://github.com/morluto/rea/issues/1774), closed as completed, concerns Windows batch-launcher version-probe failure; this fixture passes version admission and then refuses decoding. - [#3068](https://github.com/morluto/rea/issues/3068), closed as completed, concerns incorrect Apktool availability in `binary_session`; this report concerns the target path in a decode refusal. - [#2563](https://github.com/morluto/rea/issues/2563), closed as completed, concerns locale discovery in successful decode output; this report concerns error output after failed decoding. - [#1813](https://github.com/morluto/rea/issues/1813) proposes split APK set identity and relationship evidence; this report preserves the identity of one selected APK in an existing operation's refusal. - [#717](https://github.com/morluto/rea/issues/717) covers the Android integration roadmap; it does not report this specific decode-refusal path substitution.
Author
Owner

@CassieFox23 commented on GitHub (Oct 14, 2026):

TL;DR: The bug also happens on Linux. On main efbc486b9 the error names the launcher, not the APK. A fix is open in #3462.

I ran your recipe on Linux with Node 24.18.0. I used main (efbc486b9) and your fake launcher, so no real Apktool or Java was involved.

Results

Input Exit Target in the error Matches the report
sample.apk 1 fake-apktool yes
other.apk (a second APK name) 1 fake-apktool yes
sample.zip (same bytes) 1 fake-apktool yes

Each run returned the code unsupported_target. The reason was "Could not decode resource table". The captured decode exit code was 1. The message named the launcher path. The APK path was missing. The second APK name changed nothing, so the path does not follow the caller's choice.

Your suspected cause fits the code. On main, ApktoolProvider.ts hands the launcher command to the refusal as its path. Pull request #3462 passes the input path there instead. I read its diff and did not run it.

Not run

Edit: I tested #3462, and the fix works. Still not run: a real Apktool or Java, Node 22 and 26, and macOS.

  • The pinned commit 8dde6370d, because main already shows the bug.
  • Pull request #3462 (read only). Run since, on head 08d3d0d3a.
  • A real Apktool or Java.
  • Node 22 and Node 26.
  • macOS.
  • Any test files.

Thanks, Coy Geek, for the clear report and the fixture.

Commands
git clone https://github.com/morluto/rea.git
git checkout efbc486b9bf8ab46da16fdd6ed88128849e30e81
npm ci
npm run build:cached
env HOME=/tmp/i3340/home/sample.apk TMPDIR=/tmp/i3340/case \
  REA_APKTOOL_COMMAND=/tmp/i3340/case/fake-apktool \
  node scripts/rea.mjs decode-android-resources /tmp/i3340/case/sample.apk --format json

I built from a tree archived from that commit, not a clone. Exit codes: archive 0, npm ci 0, build 0. Each of the three CLI runs exited 1, as in the report. I used a fresh empty HOME for each run. The fake launcher is your script word for word.

Test setup
  • Box: Nobara 44 (Fedora 44 base), Linux 7.2.9
  • CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads
  • RAM: 32 GB DDR4-3600
  • Node: 24.18.0
  • Head / base: efbc486b9 / 8dde6370d (the report's commit; not built)

🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊

<!-- gh-comment-id:6176657565 --> @CassieFox23 commented on GitHub (Oct 14, 2026): **TL;DR:** The bug also happens on Linux. On main efbc486b9 the error names the launcher, not the APK. A fix is open in #3462. I ran your recipe on Linux with Node 24.18.0. I used main (efbc486b9) and your fake launcher, so no real Apktool or Java was involved. ### Results | Input | Exit | Target in the error | Matches the report | |---|---|---|---| | sample.apk | 1 | fake-apktool | yes | | other.apk (a second APK name) | 1 | fake-apktool | yes | | sample.zip (same bytes) | 1 | fake-apktool | yes | Each run returned the code `unsupported_target`. The reason was "Could not decode resource table". The captured decode exit code was 1. The message named the launcher path. The APK path was missing. The second APK name changed nothing, so the path does not follow the caller's choice. Your suspected cause fits the code. On main, `ApktoolProvider.ts` hands the launcher command to the refusal as its path. Pull request #3462 passes the input path there instead. I read its diff and did not run it. ### Not run Edit: I tested #3462, and [the fix works](https://github.com/morluto/rea/pull/3462#issuecomment-6181522470). Still not run: a real Apktool or Java, Node 22 and 26, and macOS. - The pinned commit 8dde6370d, because main already shows the bug. - ~~Pull request #3462 (read only).~~ Run since, on head 08d3d0d3a. - A real Apktool or Java. - Node 22 and Node 26. - macOS. - Any test files. Thanks, Coy Geek, for the clear report and the fixture. <details> <summary>Commands</summary> ```sh git clone https://github.com/morluto/rea.git git checkout efbc486b9bf8ab46da16fdd6ed88128849e30e81 npm ci npm run build:cached env HOME=/tmp/i3340/home/sample.apk TMPDIR=/tmp/i3340/case \ REA_APKTOOL_COMMAND=/tmp/i3340/case/fake-apktool \ node scripts/rea.mjs decode-android-resources /tmp/i3340/case/sample.apk --format json ``` I built from a tree archived from that commit, not a clone. Exit codes: archive 0, npm ci 0, build 0. Each of the three CLI runs exited 1, as in the report. I used a fresh empty HOME for each run. The fake launcher is your script word for word. </details> <details> <summary>Test setup</summary> - Box: Nobara 44 (Fedora 44 base), Linux 7.2.9 - CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads - RAM: 32 GB DDR4-3600 - Node: 24.18.0 - Head / base: efbc486b9 / 8dde6370d (the report's commit; not built) </details> 🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊
Author
Owner

@morluto commented on GitHub (Oct 17, 2026):

Fixed in cc038da218cada68c87700477aba3db4739791ff on main, adapting Peter's target-path correction from PR #3462 (08d3d0d3a0e816a2a73fd8224ede86a62e60ed34) with co-author credit.

The caller-selected APK now owns unsupported_target message/path attribution. The producing boundary also distinguishes fully collected ordinary nonzero decode exits from launcher startup, ownership preparation, capture, signal and incomplete-output failures; those remain provider failures. One shared projection preserves command or compiler output in probe/decode errors. Cleanup recovery, physical-failure priority and snapshot provenance remain intact.

Validation: 131 focused tests in 12 files; final 50 source checks; build, typecheck/lint, docs and hooks passed. Ten controls fail on clean baseline 4844ae812, covering two named targets, actual launcher disappearance after version admission, and controlled command outcome kinds. Two read-only source reviews found no blockers.

24 tiny completed expected-refusal tasks exercised the actual built CLI and stdio MCP with two selected paths (including a space). CLI exit 1 and MCP isError were expected and checked. Only projected message and details.path differ; every other projected field matches before/after, including captured output, reason and partial snapshot facts.

Task Elapsed seconds CPU seconds Sampled family RSS MiB Full-reply token estimate
CLI, first target 0.875 → 0.871 1.099 → 1.093 492.4 → 492.9 266 → 270
CLI, second target 0.874 → 0.871 1.098 → 1.104 493.5 → 492.1 266 → 270
MCP, first target 1.034 → 1.022 1.275 → 1.265 614.3 → 612.8 310 → 314
MCP, second target 1.079 → 1.030 1.320 → 1.267 615.0 → 611.7 310 → 314

No speedup claim. Two alternating warm-filesystem repeats plus first processes; caches were not flushed. CPU uses wait4; sampled RSS may miss brief/reparented members. cl100k estimates exclude catalog discovery, arguments, retries and agent continuations. Correcting the path adds four reply tokens in these fixtures. The final test fixture was adjusted to match the producer's optional-true field type after measurement; measured runtime sources match the commit.

Authored inert launchers and controlled outcome seams only: actual Apktool/JVM, natural startup/capture failures, native Windows and cold helper compilation remain unverified. Heavy stress/profiling remained stopped. Qualified locale discovery (#4612) is separate work.

<!-- gh-comment-id:6235255661 --> @morluto commented on GitHub (Oct 17, 2026): Fixed in `cc038da218cada68c87700477aba3db4739791ff` on main, adapting Peter's target-path correction from PR #3462 (`08d3d0d3a0e816a2a73fd8224ede86a62e60ed34`) with co-author credit. The caller-selected APK now owns `unsupported_target` message/path attribution. The producing boundary also distinguishes fully collected ordinary nonzero decode exits from launcher startup, ownership preparation, capture, signal and incomplete-output failures; those remain provider failures. One shared projection preserves command or compiler output in probe/decode errors. Cleanup recovery, physical-failure priority and snapshot provenance remain intact. Validation: 131 focused tests in 12 files; final 50 source checks; build, typecheck/lint, docs and hooks passed. Ten controls fail on clean baseline `4844ae812`, covering two named targets, actual launcher disappearance after version admission, and controlled command outcome kinds. Two read-only source reviews found no blockers. 24 tiny completed expected-refusal tasks exercised the actual built CLI and stdio MCP with two selected paths (including a space). CLI exit 1 and MCP isError were expected and checked. Only projected `message` and `details.path` differ; every other projected field matches before/after, including captured output, reason and partial snapshot facts. | Task | Elapsed seconds | CPU seconds | Sampled family RSS MiB | Full-reply token estimate | | --- | --- | --- | --- | --- | | CLI, first target | 0.875 → 0.871 | 1.099 → 1.093 | 492.4 → 492.9 | 266 → 270 | | CLI, second target | 0.874 → 0.871 | 1.098 → 1.104 | 493.5 → 492.1 | 266 → 270 | | MCP, first target | 1.034 → 1.022 | 1.275 → 1.265 | 614.3 → 612.8 | 310 → 314 | | MCP, second target | 1.079 → 1.030 | 1.320 → 1.267 | 615.0 → 611.7 | 310 → 314 | No speedup claim. Two alternating warm-filesystem repeats plus first processes; caches were not flushed. CPU uses wait4; sampled RSS may miss brief/reparented members. cl100k estimates exclude catalog discovery, arguments, retries and agent continuations. Correcting the path adds four reply tokens in these fixtures. The final test fixture was adjusted to match the producer's optional-true field type after measurement; measured runtime sources match the commit. Authored inert launchers and controlled outcome seams only: actual Apktool/JVM, natural startup/capture failures, native Windows and cold helper compilation remain unverified. Heavy stress/profiling remained stopped. Qualified locale discovery (#4612) is separate work.
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#1207
No description provided.