From 4bf522672468cf21ff09a804ff287f0195501712 Mon Sep 17 00:00:00 2001 From: Yi-111-a <153097222+Yi-111-a@users.noreply.github.com> Date: Sat, 14 Nov 2026 07:29:08 +0000 Subject: [PATCH] fix(apktool): treat an EPERM launcher spawn as not executable A launcher whose exec is refused by an operating-system security policy surfaces EPERM, not EACCES. It was reported as a missing launcher, so the recovery pointed at installing apktool instead of fixing execute permission. EPERM now joins EACCES as command_not_executable. Squashed from #5180 and rebased onto main. Supersedes #5180 (follow-up to #5177) Co-Authored-By: gensokyo-dev Co-Authored-By: Claude Opus 5.5 --- src/apktool/ApktoolConfiguration.ts | 17 ++++++++++++----- src/apktool/ApktoolVersionProbe.test.ts | 1 + 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/apktool/ApktoolConfiguration.ts b/src/apktool/ApktoolConfiguration.ts index fb6f271dc3..b8458ec412 100644 --- a/src/apktool/ApktoolConfiguration.ts +++ b/src/apktool/ApktoolConfiguration.ts @@ -522,17 +522,23 @@ const versionProbeFailure = ( ...apktoolVersionProbeTimeoutGuidance(), }, ); + // EPERM is the other errno a refused exec can surface (for example a launcher + // blocked by an operating-system security policy), so it joins EACCES as a + // permission failure, not a missing launcher. if ( cause instanceof Error && "code" in cause && - (cause.code === "ENOENT" || cause.code === "EACCES") - ) + (cause.code === "ENOENT" || + cause.code === "EACCES" || + cause.code === "EPERM") + ) { + const execDenied = cause.code === "EACCES" || cause.code === "EPERM"; return new ApktoolConfigurationFailure( - cause.code === "EACCES" ? "command_not_executable" : "command_missing", - cause.code === "EACCES" + execDenied ? "command_not_executable" : "command_missing", + execDenied ? `Execution of the selected apktool launcher was denied: ${selection.command}` : `The selected apktool launcher was not found: ${selection.command}`, - cause.code === "EACCES" + execDenied ? APKTOOL_NOT_EXECUTABLE_REMEDIATION : APKTOOL_MISSING_REMEDIATION, { @@ -542,6 +548,7 @@ const versionProbeFailure = ( }, { cause }, ); + } const snapshot = cause instanceof OwnedCommandFailure ? cause.snapshot : null; if (cause instanceof OwnedCommandFailure && cause.reason === "output-limit") return new AnalysisResourceConstraintError( diff --git a/src/apktool/ApktoolVersionProbe.test.ts b/src/apktool/ApktoolVersionProbe.test.ts index 81aa47b035..8786991d03 100644 --- a/src/apktool/ApktoolVersionProbe.test.ts +++ b/src/apktool/ApktoolVersionProbe.test.ts @@ -201,6 +201,7 @@ it.each(["linux", "win32"] as const)( it.each([ ["EACCES", "command_not_executable"], + ["EPERM", "command_not_executable"], ["ENOENT", "command_missing"], ])("distinguishes launcher spawn %s", async (code, expected) => { vi.mocked(runOwnedCommand).mockRejectedValueOnce(