fix: run brew commands on a serial queue, one at a time

runBrew dispatched onto the global concurrent queue, so overlapping
operations (refresh timer firing mid-upgrade, quick manual actions)
ran multiple brew processes at once — each able to summon its own sudo
password dialog, hence duplicate password windows. A private serial
queue makes every brew command wait for the previous one, so a second
dialog cannot appear while one is open. Log output no longer
interleaves either.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
maxsoch 2026-07-07 05:59:48 +02:00
parent 25e4476317
commit 5b1820b7e1

View file

@ -169,10 +169,15 @@ class AppDelegate: NSObject, NSApplicationDelegate {
return url return url
} }
/// Serial: brew commands run strictly one after another. Concurrent brew
/// processes could each summon their own sudo password dialog (and their
/// log output interleaved); queueing them prevents both.
static let brewQueue = DispatchQueue(label: "fr.socheleau.BrewBar.brew")
/// askpassMessage customizes the sudo dialog ("...password to <message>."); /// askpassMessage customizes the sudo dialog ("...password to <message>.");
/// defaults to naming the brew command. /// defaults to naming the brew command.
func runBrew(_ command: String, askpassMessage: String? = nil, completion: @escaping (String) -> Void = { _ in }) { func runBrew(_ command: String, askpassMessage: String? = nil, completion: @escaping (String) -> Void = { _ in }) {
DispatchQueue.global().async { Self.brewQueue.async {
let brew = self.resolveBrewPath() let brew = self.resolveBrewPath()
let cleaned = command.replacingOccurrences(of: "brew ", with: "") let cleaned = command.replacingOccurrences(of: "brew ", with: "")
let arguments = [brew] + cleaned.split(separator: " ").map(String.init) let arguments = [brew] + cleaned.split(separator: " ").map(String.init)