Cupertino 1.2.2
Version 1.2.2 ·
Three fixes to how the app survives things going wrong around it.
Fixed
The menu bar stops responding.
StatusModel.refresh()asked TCC about every Apple Events surface synchronously, and SwiftUI runsrefresh()inside a layout pass.AEDeterminePermissionToAutomateTargetdoes not prompt when told not to, but not prompting is not the same as not blocking: it is a synchronous IPC and can park on a semaphore indefinitely. Measured on 1.2.1 build 192 — two samples 60 seconds apart with the same stack, 2396 of 2396 samples, 11 seconds of CPU across 11 hours. The run loop never came back, so clicks did nothing and no bridge could finish a handshake. The permission read now happens off the main thread and publishes back, so a stalled answer leaves the previous glyph on screen instead of freezing the app.A bridge could abort while reporting a broken pipe.
warnwrote through-[NSFileHandle writeData:], which reports a failed write by raising an Objective-C exception rather than returning an error — and Swift cannot catch it. An MCP host that exits closes stdout, the socket and stderr together, so the run that most needed a diagnostic was exactly the run where emitting it killed the relay.warnandhostLognow usewrite(2)and drop the line rather than the process.Two copies of Cupertino no longer fight over the socket.
openSocketunlinked the socket path unconditionally before binding. That clears a stale entry after a crash, and it also evicts a live one: with a checkout's Debug build and the installed app both reachable from MCP config, whichever started last took the path silently and left the other accepting connections nobody could reach — no error anywhere, and a menu bar that looked healthy. The host now connects to the path first. An answer means another copy is serving and this one refuses with a message that names the situation; no answer means the entry really is stale and it is cleared as before.