fix(android): report a rejected relative JADX JAR instead of an unset one (#5862)

A supplied non-absolute REA_JADX_MCP_JAR was folded into the unset case,
emitting not_configured with jar_configured: false and dropping the
caller-selected value. The JAR parser now keeps the two outcomes distinct:
unset stays not_configured, while a supplied non-absolute value (including
an empty string) fails with open_options_invalid, jar_configured: true, the
exact rejected jar_path and the absolute-path requirement. CLI, MCP and
doctor derive their output from that one boundary result.

Fixes #5844

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kaoru0822
2026-10-21 04:57:55 -07:00
committed by GitHub
parent 7f9a492a8d
commit d124809950
3 changed files with 90 additions and 1 deletions
+10 -1
View File
@@ -544,13 +544,22 @@ const resolveJar = async (
environment: Readonly<Record<string, string | undefined>>,
): Promise<string> => {
const jar = environment.REA_JADX_MCP_JAR;
if (jar === undefined || !isAbsolute(jar))
if (jar === undefined)
throw configurationFailure(
"not_configured",
JADX_JAR_CONFIGURATION_REMEDIATION,
{ jar_configured: false },
"jar",
);
// A supplied selection stays configured even when rejected, so callers can
// tell it from an omitted setting and see the exact value REA refused.
if (!isAbsolute(jar))
throw configurationFailure(
"open_options_invalid",
`REA_JADX_MCP_JAR must be an absolute path; received ${JSON.stringify(jar)}. ${JADX_JAR_CONFIGURATION_REMEDIATION}`,
{ jar_configured: true, jar_path: jar, path_requirement: "absolute" },
"jar",
);
try {
await access(jar, constants.R_OK);
if (!(await stat(jar)).isFile())
@@ -76,6 +76,61 @@ for (const { jarConfigured, setting } of [
},
);
cliTest.skipIf(process.platform === "win32")(
"distinguishes a rejected relative REA_JADX_MCP_JAR from an unset one through CLI and MCP",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-android-relative-jar-");
const apk = join(root, "fixture.apk");
await writeOrderedZip(apk, ["AndroidManifest.xml"]);
const run = async (environment: Record<string, string>) => {
const cliResult = await cli.run({
arguments: ["inspect-android-package", apk, "--json"],
environment: { REA_LOG_LEVEL: "silent", ...environment },
timeoutMs: 10_000,
});
expect(cliResult.exitCode).toBe(1);
const client = await connect({ REA_LOG_LEVEL: "silent", ...environment });
const response = await client.callTool({
name: "inspect_android_package",
arguments: { path: apk },
});
expect(response.isError).toBe(true);
expect(parseMcpToolError(response)).toEqual({ error: cliResult.json });
return cliResult.json as {
code: string;
message: string;
details: { diagnostics: Record<string, unknown> };
};
};
const relative = "./tools/jadx-headless-mcp.jar";
const rejected = await run({ REA_JADX_MCP_JAR: relative });
expect(rejected.code).toBe("capability_unavailable");
expect(rejected.details.diagnostics).toEqual({
jar_configured: true,
jar_path: relative,
path_requirement: "absolute",
failure_kind: "jar",
failure_code: "open_options_invalid",
});
expect(rejected.message).toContain(`received "${relative}"`);
const empty = await run({ REA_JADX_MCP_JAR: "" });
expect(empty.details.diagnostics).toMatchObject({
jar_configured: true,
jar_path: "",
failure_code: "open_options_invalid",
});
const unset = await run({});
expect(unset.details.diagnostics).toEqual({
jar_configured: false,
failure_kind: "jar",
failure_code: "not_configured",
});
},
);
cliTest.skipIf(process.platform !== "linux")(
"preserves the missing firmware command reason through both caller paths",
async ({ cli }) => {
+25
View File
@@ -133,6 +133,31 @@ it("publishes no registration environment when REA_JADX_MCP_JAR is unset", async
});
});
it("reports a relative REA_JADX_MCP_JAR as a rejected selection, not an unset one", async () => {
const inspection = await inspectJadxProvider({
REA_JADX_MCP_JAR: "tools/jadx-headless-mcp.jar",
});
expect(inspection).toMatchObject({
id: "jadx",
configured: true,
available: false,
registrationEnvironment: {},
checks: [
{
name: "configuration",
ok: false,
code: "open_options_invalid",
classification: "config_drift",
},
],
});
expect(inspection.checks[0]?.detail).toContain(
'REA_JADX_MCP_JAR must be an absolute path; received "tools/jadx-headless-mcp.jar".',
);
expect(inspection.checks[0]?.remediation).toContain("REA_JADX_MCP_JAR");
});
it("publishes no registration environment when the configured JAR fails validation", async () => {
const root = await createTestTempDirectory("rea-jadx-doctor-invalid-");
const jar = join(root, "engine.jar");