[GH-ISSUE #2672] fix(cli): documented string omission flag stops Android resource decoding #975

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

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

Runtime or environment

Verified against source revision a2d8b94dd5 using Node.js 24.18.0, npm 11.16.0 and the locked incur 0.6.1 dependency. The observations below use the source command registration with an inert provider. The published rea-agents 6.4.0 executable and a real Apktool launcher were not tested. Source recheck at 3ae826ceba confirms that the documented option, command registration and locked parser still have the same flag rejection; no new runtime reproduction was run.

Summary

A caller following the Android resource guide cannot run the decode command with its documented string omission flag. Help offers --no-strings, but passing it produces an unknown-flag error before the decode request reaches the provider. The failure does not depend on APK contents or launcher availability.

Steps to reproduce

With the repository's locked dependencies available, save the following test as src/apktoolOptions.repro.test.ts. It exercises the production command registration and its parser, and substitutes an inert provider so no APK or launcher is needed. These test operations were executed locally.

import { expect, it } from "vitest";
import { Cli } from "incur";
import pino from "pino";
import { registerApktoolCommands } from "./cli/apktoolCommands.js";
import { presentOmittableDefaults } from "./cli/omittableDefaults.js";
import { createAnalysisExecution } from "./application/AnalysisProvider.js";
import type { ApktoolResourceAnalysisPort } from "./application/apktool/ApktoolResourceAnalysisPort.js";
import type { ApktoolRequest } from "./domain/apktool/apktoolResourceAnalysis.js";
import { ApktoolResourceAnalysisService } from "./application/apktool/ApktoolResourceAnalysisService.js";
import { ok } from "./domain/result.js";

it("records advertised no-strings parsing and provider dispatch", async () => {
  for (const flags of [["--help"], [], ["--no-strings"], ["--no-strings=true"], ["--no-no-strings"]]) {
    const requests: ApktoolRequest[] = [];
    const provider: ApktoolResourceAnalysisPort = {
      async inspectAvailability() { throw new Error("unexpected probe"); },
      async close() {},
      async execute(request) {
        requests.push(request);
        return ok(createAnalysisExecution({ strings: [] }, { id: "apktool", name: "Apktool", version: null }));
      },
    };
    const cli = Cli.create("rea", { sync: false });
    registerApktoolCommands(cli, pino({ level: "silent" }), {}, provider);
    presentOmittableDefaults(cli);
    let stdout = "";
    let exitCode = 0;
    const argv = ["decode-android-resources", "sample.apk", ...flags, ...(flags.includes("--help") ? [] : ["--json"])];
    await cli.serve(argv, { env: {}, stdout: (text) => { stdout += text; }, exit: (code) => { exitCode = code; } });
    if (flags.includes("--help")) {
      expect(stdout).toContain("--no-strings");
      expect(stdout).toContain("Skip projecting string resources");
      expect(requests).toHaveLength(0);
      console.log(JSON.stringify({ argv, exitCode, advertised: "--no-strings  Skip projecting string resources", requests }));
    } else {
      console.log(JSON.stringify({ argv, exitCode, output: JSON.parse(stdout), requests }));
      if (flags[0] === "--no-strings" || flags[0] === "--no-strings=true") {
        expect(exitCode).toBe(1);
        expect(JSON.parse(stdout)).toMatchObject({ code: "UNKNOWN", message: `Unknown flag: ${flags[0]}` });
        expect(requests).toHaveLength(0);
      } else {
        expect(exitCode).toBe(0);
        expect(requests).toEqual([{ operation: "decode_android_resources", input: { path: "sample.apk", include_strings: true } }]);
      }
    }
  }
});

it("admits include_strings false through the shared operation", async () => {
  const requests: ApktoolRequest[] = [];
  const provider: ApktoolResourceAnalysisPort = {
    async inspectAvailability() { throw new Error("unexpected probe"); },
    async close() {},
    async execute(request) {
      requests.push(request);
      return ok(createAnalysisExecution({ strings: [] }, { id: "apktool", name: "Apktool", version: null }));
    },
  };
  const result = await new ApktoolResourceAnalysisService(provider).execute("decode_android_resources", { path: "sample.apk", include_strings: false });
  expect(result.ok).toBe(true);
  expect(requests).toEqual([{ operation: "decode_android_resources", input: { path: "sample.apk", include_strings: false } }]);
  console.log(JSON.stringify({ sharedOperation: "decode_android_resources", input: { path: "sample.apk", include_strings: false }, accepted: result.ok, requests }));
});

Run this focused source test, then remove its temporary file:

npm run test:focused -- src/apktoolOptions.repro.test.ts
rm src/apktoolOptions.repro.test.ts

The test uses decode-android-resources sample.apk --no-strings --json as its failing caller input. sample.apk is only an argument value; the parser rejects the flag before the provider can inspect that path. The assertions record the current defect rather than the desired restored behavior.

Expected behavior

The bare flag shown by help and the resource guide should be accepted and dispatch a decode request with include_strings: false. Omitting the flag should continue to dispatch include_strings: true.

Actual behavior

The focused test passed both observation tests. Help included --no-strings and Skip projecting string resources. Serving the bare flag returned captured exit code 1, made no provider calls and printed:

{"code":"UNKNOWN","message":"Unknown flag: --no-strings"}

The equals variant also returned captured exit code 1 with Unknown flag: --no-strings=true and no provider calls. The default and --no-no-strings controls returned exit code 0 and each dispatched include_strings: true. A direct call to the shared service with include_strings: false dispatched false successfully. No real APK decoding or executable subprocess invocation was performed.

Evidence

  • Expected source: docs/apktool-resource-analysis.md:39 documents the flag; src/cli/apktoolCommands.ts:65-74 declares its help description and maps it to the shared projection input.
  • Failure source: The executed test above captures both unknown-flag errors and zero provider calls. In the locked dependency, node_modules/incur/src/Parser.ts:24-29 strips the negation prefix before looking up an option, so this token requires a strings option that the command does not register.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

src/cli.ts registers this command and applies presentOmittableDefaults, which the test also applies. The shared schema accepts the boolean at src/domain/apktool/apktoolResourceAnalysis.ts:16-20; src/server/registerApktoolTools.ts exposes that service through decode_android_resources. The production provider uses the boolean at src/apktool/ApktoolProvider.ts:666 to decide whether to read the string table. These downstream facts are source observations; the inert provider establishes request dispatch rather than actual string extraction.

Related: #2563 concerns incorrect locale discovery after decoding, which has a separate reproduction and restoration check.

Restoration check

Repeat the registered-command test with the bare flag. It should return a successful parser result and dispatch exactly one decode request with include_strings: false, rather than producing the unknown-flag error. Preserve the unflagged control, which must still dispatch true. With an owned APK containing at least one string entry and an available launcher, the flagged result should omit projected strings while the unflagged result retains them. That final provider check has not been executed here.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A documented output-selection option blocks this specific CLI invocation before decoding. The default decode route remains reachable, and no data loss or incorrect completed extraction was observed.
  • Workaround status: available
  • Workaround basis: The MCP operation exposes include_strings: false, and the shared service's acceptance and dispatch of that exact input were observed with an inert provider. Using it requires a caller able to use MCP; real MCP transport and launcher behavior were not exercised.
Originally created by @coygeek on GitHub (Oct 13, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2672 ## Runtime or environment Verified against source revision a2d8b94dd50dab8d966239dd63ab84718aa8b4b4 using Node.js 24.18.0, npm 11.16.0 and the locked incur 0.6.1 dependency. The observations below use the source command registration with an inert provider. The published rea-agents 6.4.0 executable and a real Apktool launcher were not tested. Source recheck at 3ae826ceba6f25b08bce50bcb51001209bc16b57 confirms that the documented option, command registration and locked parser still have the same flag rejection; no new runtime reproduction was run. ## Summary A caller following the Android resource guide cannot run the decode command with its documented string omission flag. Help offers `--no-strings`, but passing it produces an unknown-flag error before the decode request reaches the provider. The failure does not depend on APK contents or launcher availability. ## Steps to reproduce With the repository's locked dependencies available, save the following test as `src/apktoolOptions.repro.test.ts`. It exercises the production command registration and its parser, and substitutes an inert provider so no APK or launcher is needed. These test operations were executed locally. ```typescript import { expect, it } from "vitest"; import { Cli } from "incur"; import pino from "pino"; import { registerApktoolCommands } from "./cli/apktoolCommands.js"; import { presentOmittableDefaults } from "./cli/omittableDefaults.js"; import { createAnalysisExecution } from "./application/AnalysisProvider.js"; import type { ApktoolResourceAnalysisPort } from "./application/apktool/ApktoolResourceAnalysisPort.js"; import type { ApktoolRequest } from "./domain/apktool/apktoolResourceAnalysis.js"; import { ApktoolResourceAnalysisService } from "./application/apktool/ApktoolResourceAnalysisService.js"; import { ok } from "./domain/result.js"; it("records advertised no-strings parsing and provider dispatch", async () => { for (const flags of [["--help"], [], ["--no-strings"], ["--no-strings=true"], ["--no-no-strings"]]) { const requests: ApktoolRequest[] = []; const provider: ApktoolResourceAnalysisPort = { async inspectAvailability() { throw new Error("unexpected probe"); }, async close() {}, async execute(request) { requests.push(request); return ok(createAnalysisExecution({ strings: [] }, { id: "apktool", name: "Apktool", version: null })); }, }; const cli = Cli.create("rea", { sync: false }); registerApktoolCommands(cli, pino({ level: "silent" }), {}, provider); presentOmittableDefaults(cli); let stdout = ""; let exitCode = 0; const argv = ["decode-android-resources", "sample.apk", ...flags, ...(flags.includes("--help") ? [] : ["--json"])]; await cli.serve(argv, { env: {}, stdout: (text) => { stdout += text; }, exit: (code) => { exitCode = code; } }); if (flags.includes("--help")) { expect(stdout).toContain("--no-strings"); expect(stdout).toContain("Skip projecting string resources"); expect(requests).toHaveLength(0); console.log(JSON.stringify({ argv, exitCode, advertised: "--no-strings Skip projecting string resources", requests })); } else { console.log(JSON.stringify({ argv, exitCode, output: JSON.parse(stdout), requests })); if (flags[0] === "--no-strings" || flags[0] === "--no-strings=true") { expect(exitCode).toBe(1); expect(JSON.parse(stdout)).toMatchObject({ code: "UNKNOWN", message: `Unknown flag: ${flags[0]}` }); expect(requests).toHaveLength(0); } else { expect(exitCode).toBe(0); expect(requests).toEqual([{ operation: "decode_android_resources", input: { path: "sample.apk", include_strings: true } }]); } } } }); it("admits include_strings false through the shared operation", async () => { const requests: ApktoolRequest[] = []; const provider: ApktoolResourceAnalysisPort = { async inspectAvailability() { throw new Error("unexpected probe"); }, async close() {}, async execute(request) { requests.push(request); return ok(createAnalysisExecution({ strings: [] }, { id: "apktool", name: "Apktool", version: null })); }, }; const result = await new ApktoolResourceAnalysisService(provider).execute("decode_android_resources", { path: "sample.apk", include_strings: false }); expect(result.ok).toBe(true); expect(requests).toEqual([{ operation: "decode_android_resources", input: { path: "sample.apk", include_strings: false } }]); console.log(JSON.stringify({ sharedOperation: "decode_android_resources", input: { path: "sample.apk", include_strings: false }, accepted: result.ok, requests })); }); ``` Run this focused source test, then remove its temporary file: ```sh npm run test:focused -- src/apktoolOptions.repro.test.ts rm src/apktoolOptions.repro.test.ts ``` The test uses `decode-android-resources sample.apk --no-strings --json` as its failing caller input. `sample.apk` is only an argument value; the parser rejects the flag before the provider can inspect that path. The assertions record the current defect rather than the desired restored behavior. ## Expected behavior The bare flag shown by help and the resource guide should be accepted and dispatch a decode request with `include_strings: false`. Omitting the flag should continue to dispatch `include_strings: true`. ## Actual behavior The focused test passed both observation tests. Help included `--no-strings` and `Skip projecting string resources`. Serving the bare flag returned captured exit code 1, made no provider calls and printed: ```json {"code":"UNKNOWN","message":"Unknown flag: --no-strings"} ``` The equals variant also returned captured exit code 1 with `Unknown flag: --no-strings=true` and no provider calls. The default and `--no-no-strings` controls returned exit code 0 and each dispatched `include_strings: true`. A direct call to the shared service with `include_strings: false` dispatched false successfully. No real APK decoding or executable subprocess invocation was performed. ## Evidence - Expected source: `docs/apktool-resource-analysis.md:39` documents the flag; `src/cli/apktoolCommands.ts:65-74` declares its help description and maps it to the shared projection input. - Failure source: The executed test above captures both unknown-flag errors and zero provider calls. In the locked dependency, `node_modules/incur/src/Parser.ts:24-29` strips the negation prefix before looking up an option, so this token requires a `strings` option that the command does not register. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete `src/cli.ts` registers this command and applies `presentOmittableDefaults`, which the test also applies. The shared schema accepts the boolean at `src/domain/apktool/apktoolResourceAnalysis.ts:16-20`; `src/server/registerApktoolTools.ts` exposes that service through `decode_android_resources`. The production provider uses the boolean at `src/apktool/ApktoolProvider.ts:666` to decide whether to read the string table. These downstream facts are source observations; the inert provider establishes request dispatch rather than actual string extraction. Related: #2563 concerns incorrect locale discovery after decoding, which has a separate reproduction and restoration check. ## Restoration check Repeat the registered-command test with the bare flag. It should return a successful parser result and dispatch exactly one decode request with `include_strings: false`, rather than producing the unknown-flag error. Preserve the unflagged control, which must still dispatch true. With an owned APK containing at least one string entry and an available launcher, the flagged result should omit projected strings while the unflagged result retains them. That final provider check has not been executed here. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: A documented output-selection option blocks this specific CLI invocation before decoding. The default decode route remains reachable, and no data loss or incorrect completed extraction was observed. - Workaround status: available - Workaround basis: The MCP operation exposes `include_strings: false`, and the shared service's acceptance and dispatch of that exact input were observed with an inert provider. Using it requires a caller able to use MCP; real MCP transport and launcher behavior were not exercised.
bsakaguchi 2026-10-15 18:30:01 +00:00
  • closed this issue
  • added the
    bug
    label
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#975
No description provided.