From 595bd7753581c887da4e1ee6d44f078382168f5d Mon Sep 17 00:00:00 2001 From: Roland Kaibull <6277393+bubleg@users.noreply.github.com> Date: Wed, 7 Oct 2026 05:33:51 +0200 Subject: [PATCH 1/2] fix: run every administrator step even when one fails --- Sources/removemacai/SelfTest.swift | 4 ++++ Sources/removemacai/Shell.swift | 9 +++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Sources/removemacai/SelfTest.swift b/Sources/removemacai/SelfTest.swift index e260482..2c3a51b 100644 --- a/Sources/removemacai/SelfTest.swift +++ b/Sources/removemacai/SelfTest.swift @@ -217,6 +217,10 @@ func selfTest() -> Bool { program: "/Library/PrivilegedHelperTools/dev.orbstack.OrbStack.privhelper", system: true) check(script.owner == "sync.sh" && helper.owner == "OrbStack", "background items are named after what they run") + let ranAll = Shell.run("/bin/sh", ["-c", Shell.adminScript(["false", "echo second"])]) + check(!ranAll.ok && ranAll.output.contains("second"), "one failed administrator step doesn't skip the rest") + check(Shell.run("/bin/sh", ["-c", Shell.adminScript(["true", "true"])]).ok, "administrator steps succeed together") + print(failed == 0 ? Term.green("all checks passed") : Term.red("\(failed) failed")) return failed == 0 } diff --git a/Sources/removemacai/Shell.swift b/Sources/removemacai/Shell.swift index 7f243d7..8e0925f 100644 --- a/Sources/removemacai/Shell.swift +++ b/Sources/removemacai/Shell.swift @@ -26,10 +26,11 @@ enum Shell { static func quote(_ s: String) -> String { "'" + s.replacingOccurrences(of: "'", with: "'\\''") + "'" } /// Runs shell commands as root after one macOS password prompt. From a - /// terminal, sudo asks instead. + /// terminal, sudo asks instead. Every command runs even when an earlier one + /// fails; the result fails if any of them did. static func admin(_ commands: [String], prompt: String) -> Result { guard !commands.isEmpty else { return Result(status: 0, output: "") } - let script = commands.joined(separator: " && ") + let script = adminScript(commands) if getuid() == 0 { return run("/bin/sh", ["-c", script]) } if isatty(STDIN_FILENO) == 1 && !Shell.inApp { return run("/usr/bin/sudo", ["/bin/sh", "-c", script]) @@ -41,6 +42,10 @@ enum Shell { ]) } + static func adminScript(_ commands: [String]) -> String { + (["rc=0"] + commands.map { "{ \($0); } || rc=1" } + ["exit $rc"]).joined(separator: "; ") + } + /// A readable reason for a failed administrator step. osascript prefixes /// "0:390: execution error:" and appends the error number. static func adminError(_ result: Result) -> String { From e2243d5e8e4ad71763bc68fea17bb0d118963e38 Mon Sep 17 00:00:00 2001 From: Roland Kaibull <6277393+bubleg@users.noreply.github.com> Date: Wed, 7 Oct 2026 05:51:13 +0200 Subject: [PATCH 2/2] fix: run each administrator step in its own subshell --- Sources/removemacai/SelfTest.swift | 2 ++ Sources/removemacai/Shell.swift | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/removemacai/SelfTest.swift b/Sources/removemacai/SelfTest.swift index 2c3a51b..6db65ec 100644 --- a/Sources/removemacai/SelfTest.swift +++ b/Sources/removemacai/SelfTest.swift @@ -219,6 +219,8 @@ func selfTest() -> Bool { let ranAll = Shell.run("/bin/sh", ["-c", Shell.adminScript(["false", "echo second"])]) check(!ranAll.ok && ranAll.output.contains("second"), "one failed administrator step doesn't skip the rest") + let exited = Shell.run("/bin/sh", ["-c", Shell.adminScript(["exit 3", "echo after-exit"])]) + check(!exited.ok && exited.output.contains("after-exit"), "a step that exits doesn't stop the rest") check(Shell.run("/bin/sh", ["-c", Shell.adminScript(["true", "true"])]).ok, "administrator steps succeed together") print(failed == 0 ? Term.green("all checks passed") : Term.red("\(failed) failed")) diff --git a/Sources/removemacai/Shell.swift b/Sources/removemacai/Shell.swift index 8e0925f..0f2895a 100644 --- a/Sources/removemacai/Shell.swift +++ b/Sources/removemacai/Shell.swift @@ -43,7 +43,7 @@ enum Shell { } static func adminScript(_ commands: [String]) -> String { - (["rc=0"] + commands.map { "{ \($0); } || rc=1" } + ["exit $rc"]).joined(separator: "; ") + (["rc=0"] + commands.map { "( \($0) ) || rc=1" } + ["exit $rc"]).joined(separator: "; ") } /// A readable reason for a failed administrator step. osascript prefixes