[GH-ISSUE #6094] [Bug] ADB and Apktool capability refusals omit collected configuration diagnostics #3727

Closed
opened 2026-10-31 00:43:26 +00:00 by bsakaguchi · 0 comments
Owner

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

Before submitting

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

Area

Provider or capability discovery

Observed behavior

ADB and Apktool collect structured host-tool selection and version-probe facts but omit them from capability refusals. With executable shell substitutes that exit zero and print unrecognized version output, the public list-adb-devices --json and inspect-apktool-client --json commands each exit 1 with capability_unavailable and no details.diagnostics. The same providers retain selected command, selection source, stdout and stderr in inspectAvailability() and in the internal configuration-failure cause.

Reproduction

Executed both built CLI commands through node scripts/rea.mjs, selecting executable shell substitutes through REA_ADB_PATH and REA_APKTOOL_COMMAND. Each substitute contained:

#!/bin/sh
printf 'unrecognized version output\n'
printf 'probe stderr\n' >&2

Also executed the six focused cases below through real provider admission and error projection, with no device contact or APK decoding. Set REA_TEST_ROOT to an existing empty disposable directory, save as tests/process-global/configurationDiagnostics.test.ts, then run:

node node_modules/vitest/vitest.mjs run tests/process-global/configurationDiagnostics.test.ts --maxWorkers 1

All six cases passed as part of the focused ten-case run. The assertions characterize diagnostic loss.

import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect, it } from "vitest";
import { AdbProvider } from "../../src/adb/AdbProvider.js";
import { ApktoolProvider } from "../../src/apktool/ApktoolProvider.js";
import { projectAnalysisError } from "../../src/domain/analysisErrorProjection.js";
const root = process.env.REA_TEST_ROOT!;
for (const kind of ["adb", "apktool"] as const) {
  for (const mode of ["version", "missing", "relative"]) {
    it(`${kind} ${mode} diagnostic loss`, async () => {
      const command = join(root, kind + "-stub");
      await writeFile(command, "#!/bin/sh\nprintf 'unrecognized version output\\n'\nprintf 'probe stderr\\n' >&2\n", { mode: 0o755 });
      const selected = mode === "missing" ? join(root, "missing-" + kind) : mode === "relative" ? "relative/" + kind : command;
      const provider = kind === "adb" ? new AdbProvider({ environment: { REA_ADB_PATH: selected } }) : new ApktoolProvider({ environment: { REA_APKTOOL_COMMAND: selected } });
      try {
        const availability = await provider.inspectAvailability();
        expect(availability.status).toBe("unavailable"); if (availability.status !== "unavailable") throw new Error("Expected unavailable");
        expect(Object.keys(availability.diagnostics).length).toBeGreaterThan(0);
        const result = kind === "adb" ? await (provider as AdbProvider).execute({ operation: "list_adb_devices", input: {} }) : await (provider as ApktoolProvider).execute({ operation: "inspect_apktool_client", input: {} });
        expect(result.ok).toBe(false); if (result.ok) throw new Error("Expected refusal");
        const projected = projectAnalysisError(result.error);
        expect(projected.code).toBe("capability_unavailable"); expect(projected.details?.diagnostics).toBeUndefined();
        expect(result.error.cause).toMatchObject({ diagnostics: availability.diagnostics });
        console.log(JSON.stringify({ kind, mode, availabilityDiagnostics: availability.diagnostics, projected }));
      } finally { await provider.close(); }
    });
  }
}

Expected behavior

Keep existing producer-owned failure.diagnostics on the capability error, so projectAnalysisError(...).details.diagnostics carries configured selection, its source and actual observed probe output. Preserve existing message, remediation, refusal code and host admission rules. AnalysisErrorOptions defines these diagnostics as caller-safe facts retained through typed projection. Availability already exposes them for the same failures.

Impact and scope

P2: callers repairing provider configuration lose structured facts that REA already observed. Successful unrecognized-version probes lose stdout/stderr and selected-command/source facts from the public error. Relative-path refusals also lose the configured value. Missing-command messages still include the selected path in prose, so not every fact disappears from all output. The six cases cover both providers with unrecognized successful probes, missing commands and relative commands. Availability inspection and directly running the selected command can recover diagnostic information; no real device, Java runtime or decoder was exercised.

REA version

rea-agents 6.7.0; source revision 5241597594.

Runtime and platform

Node.js v26.12.0; macOS 27.0.1 arm64; locked Vitest 5.0.3. Built CLI plus source provider tests; executable shell substitutes, no real ADB/Apktool installation.

Target and artifact context

Host-tool configuration only. Each selected substitute prints unrecognized version stdout and diagnostic stderr and exits zero. Missing/relative selection controls run before device contact or artifact decoding.

Interface

CLI

Earliest failure stage

Setup or provider admission

Relevant output or evidence

Observed CLI results (each exit 1):

{"code":"capability_unavailable","category":"unsupported_provider","message":"The selected adb binary exited successfully but did not report a recognizable protocol or build version.","retryable":false,"remediation":{"action":"The selected adb binary exited successfully but did not report a recognizable protocol or build version."},"details":{"provider_id":"adb","operation":"list_adb_devices","reason":"The selected adb binary exited successfully but did not report a recognizable protocol or build version."}}
{"code":"capability_unavailable","category":"unsupported_provider","message":"The selected apktool launcher exited successfully but did not report a recognizable version.","retryable":false,"remediation":{"action":"The selected apktool launcher exited successfully but did not report a recognizable version."},"details":{"provider_id":"apktool","operation":"inspect_apktool_client","reason":"The selected apktool launcher exited successfully but did not report a recognizable version."}}

Availability for the same successful probes included version_probe: "unrecognized_output", stdout: "unrecognized version output\n", stderr: "probe stderr\n", plus selected_binary/path_source for ADB or selected_command/command_source for Apktool. All six provider tests confirmed that the internal error cause still contained those diagnostics while details.diagnostics was absent.

Typed outcome or recovery expected

Keep capability_unavailable for these configuration refusals, forwarding diagnostics in its supported structured field. The caller corrects the configured executable/version and retries. Do not change timeout, output-limit or execution-permission classification as part of this issue.

Analysis or suspected cause

The shared refusal helper reads only diagnostics.remediation before constructing an error with cause and userMessage. It omits the diagnostics option. The error constructor does not inherit that option from a cause, and the projection reads the typed error diagnostics. ADB produces the unrecognized-output facts before provider admission maps them through the helper. Apktool admission uses the same mapping. Forwarding the producer-owned diagnostics at that boundary covers both providers.

Additional context

Fetched upstream main d8a9673fc4; relevant helper/configuration/projection lines are unchanged. #5689 overlaps the helper omission but concerns execution-denied probes incorrectly labeled as version failures, requiring producer/message correction; this reproduction is a successful probe correctly refused for unrecognized output. #5690 concerns output overflow requiring resource_constraint. Those producer-specific corrections do not by themselves forward diagnostics for successful but unrecognized probes or relative/missing selections across both providers. This issue preserves existing classification and forwards already-collected facts through the shared helper. #1952 / PRs #1954 and #1946 fixed escaped relative configuration and generic messages, without adding structured diagnostics. #3886 concerns timeout classification; #5767 / PR #5768 concern WABT. #5971, #5978, #5981, #6045, #6046, #6047 and #6079 concern separate Android/device/input/cleanup boundaries. MCP transport and other platforms were not executed.

Originally created by @coygeek on GitHub (Oct 21, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6094 ### 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 Provider or capability discovery ### Observed behavior ADB and Apktool collect structured host-tool selection and version-probe facts but omit them from capability refusals. With executable shell substitutes that exit zero and print unrecognized version output, the public `list-adb-devices --json` and `inspect-apktool-client --json` commands each exit 1 with `capability_unavailable` and no `details.diagnostics`. The same providers retain selected command, selection source, stdout and stderr in `inspectAvailability()` and in the internal configuration-failure cause. ### Reproduction Executed both built CLI commands through `node scripts/rea.mjs`, selecting executable shell substitutes through `REA_ADB_PATH` and `REA_APKTOOL_COMMAND`. Each substitute contained: ```sh #!/bin/sh printf 'unrecognized version output\n' printf 'probe stderr\n' >&2 ``` Also executed the six focused cases below through real provider admission and error projection, with no device contact or APK decoding. Set `REA_TEST_ROOT` to an existing empty disposable directory, save as `tests/process-global/configurationDiagnostics.test.ts`, then run: ```sh node node_modules/vitest/vitest.mjs run tests/process-global/configurationDiagnostics.test.ts --maxWorkers 1 ``` All six cases passed as part of the focused ten-case run. The assertions characterize diagnostic loss. ```ts import { writeFile } from "node:fs/promises"; import { join } from "node:path"; import { expect, it } from "vitest"; import { AdbProvider } from "../../src/adb/AdbProvider.js"; import { ApktoolProvider } from "../../src/apktool/ApktoolProvider.js"; import { projectAnalysisError } from "../../src/domain/analysisErrorProjection.js"; const root = process.env.REA_TEST_ROOT!; for (const kind of ["adb", "apktool"] as const) { for (const mode of ["version", "missing", "relative"]) { it(`${kind} ${mode} diagnostic loss`, async () => { const command = join(root, kind + "-stub"); await writeFile(command, "#!/bin/sh\nprintf 'unrecognized version output\\n'\nprintf 'probe stderr\\n' >&2\n", { mode: 0o755 }); const selected = mode === "missing" ? join(root, "missing-" + kind) : mode === "relative" ? "relative/" + kind : command; const provider = kind === "adb" ? new AdbProvider({ environment: { REA_ADB_PATH: selected } }) : new ApktoolProvider({ environment: { REA_APKTOOL_COMMAND: selected } }); try { const availability = await provider.inspectAvailability(); expect(availability.status).toBe("unavailable"); if (availability.status !== "unavailable") throw new Error("Expected unavailable"); expect(Object.keys(availability.diagnostics).length).toBeGreaterThan(0); const result = kind === "adb" ? await (provider as AdbProvider).execute({ operation: "list_adb_devices", input: {} }) : await (provider as ApktoolProvider).execute({ operation: "inspect_apktool_client", input: {} }); expect(result.ok).toBe(false); if (result.ok) throw new Error("Expected refusal"); const projected = projectAnalysisError(result.error); expect(projected.code).toBe("capability_unavailable"); expect(projected.details?.diagnostics).toBeUndefined(); expect(result.error.cause).toMatchObject({ diagnostics: availability.diagnostics }); console.log(JSON.stringify({ kind, mode, availabilityDiagnostics: availability.diagnostics, projected })); } finally { await provider.close(); } }); } } ``` ### Expected behavior Keep existing producer-owned `failure.diagnostics` on the capability error, so `projectAnalysisError(...).details.diagnostics` carries configured selection, its source and actual observed probe output. Preserve existing message, remediation, refusal code and host admission rules. [AnalysisErrorOptions](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/analysisErrorBase.ts#L55-L64) defines these diagnostics as caller-safe facts retained through typed projection. Availability already exposes them for the same failures. ### Impact and scope P2: callers repairing provider configuration lose structured facts that REA already observed. Successful unrecognized-version probes lose stdout/stderr and selected-command/source facts from the public error. Relative-path refusals also lose the configured value. Missing-command messages still include the selected path in prose, so not every fact disappears from all output. The six cases cover both providers with unrecognized successful probes, missing commands and relative commands. Availability inspection and directly running the selected command can recover diagnostic information; no real device, Java runtime or decoder was exercised. ### REA version rea-agents 6.7.0; source revision 52415975942a4b9c53a817915b313ccc404f280e. ### Runtime and platform Node.js v26.12.0; macOS 27.0.1 arm64; locked Vitest 5.0.3. Built CLI plus source provider tests; executable shell substitutes, no real ADB/Apktool installation. ### Target and artifact context Host-tool configuration only. Each selected substitute prints unrecognized version stdout and diagnostic stderr and exits zero. Missing/relative selection controls run before device contact or artifact decoding. ### Interface CLI ### Earliest failure stage Setup or provider admission ### Relevant output or evidence Observed CLI results (each exit 1): ```json {"code":"capability_unavailable","category":"unsupported_provider","message":"The selected adb binary exited successfully but did not report a recognizable protocol or build version.","retryable":false,"remediation":{"action":"The selected adb binary exited successfully but did not report a recognizable protocol or build version."},"details":{"provider_id":"adb","operation":"list_adb_devices","reason":"The selected adb binary exited successfully but did not report a recognizable protocol or build version."}} {"code":"capability_unavailable","category":"unsupported_provider","message":"The selected apktool launcher exited successfully but did not report a recognizable version.","retryable":false,"remediation":{"action":"The selected apktool launcher exited successfully but did not report a recognizable version."},"details":{"provider_id":"apktool","operation":"inspect_apktool_client","reason":"The selected apktool launcher exited successfully but did not report a recognizable version."}} ``` Availability for the same successful probes included `version_probe: "unrecognized_output"`, `stdout: "unrecognized version output\n"`, `stderr: "probe stderr\n"`, plus `selected_binary`/`path_source` for ADB or `selected_command`/`command_source` for Apktool. All six provider tests confirmed that the internal error cause still contained those diagnostics while `details.diagnostics` was absent. ### Typed outcome or recovery expected Keep `capability_unavailable` for these configuration refusals, forwarding diagnostics in its supported structured field. The caller corrects the configured executable/version and retries. Do not change timeout, output-limit or execution-permission classification as part of this issue. ### Analysis or suspected cause [The shared refusal helper](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/providerConfigurationRefusal.ts#L12-L20) reads only `diagnostics.remediation` before constructing an error with `cause` and `userMessage`. It omits the `diagnostics` option. [The error constructor](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/analysisErrorBase.ts#L87-L92) does not inherit that option from a cause, and [the projection](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/analysisErrorProjection.ts#L57-L76) reads the typed error diagnostics. [ADB produces the unrecognized-output facts](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/adb/AdbConfiguration.ts#L109-L120) before [provider admission maps them through the helper](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/adb/AdbProvider.ts#L1290-L1304). [Apktool admission](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/apktool/ApktoolProvider.ts#L405-L433) uses the same mapping. Forwarding the producer-owned diagnostics at that boundary covers both providers. ### Additional context Fetched upstream main d8a9673fc44fde4bd30ff98c241886e6683580ca; relevant helper/configuration/projection lines are unchanged. [#5689](https://github.com/morluto/rea/issues/5689) overlaps the helper omission but concerns execution-denied probes incorrectly labeled as version failures, requiring producer/message correction; this reproduction is a successful probe correctly refused for unrecognized output. [#5690](https://github.com/morluto/rea/issues/5690) concerns output overflow requiring `resource_constraint`. Those producer-specific corrections do not by themselves forward diagnostics for successful but unrecognized probes or relative/missing selections across both providers. This issue preserves existing classification and forwards already-collected facts through the shared helper. #1952 / PRs #1954 and #1946 fixed escaped relative configuration and generic messages, without adding structured diagnostics. #3886 concerns timeout classification; #5767 / PR #5768 concern WABT. #5971, #5978, #5981, #6045, #6046, #6047 and #6079 concern separate Android/device/input/cleanup boundaries. MCP transport and other platforms were not executed.
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#3727
No description provided.