MacOS 26 TestFlight SIGKILLs app when updating

We're developing an Electron app for MacOS App Store. When updating our app through TestFlight, TestFlight prompts "Close This App to Update", and when I click "Continue" our app would be "Terminated" for update.

Now this is where things go wrong. On MacOS 15 our app seems to be gracefully terminating (We attached it with lldb and it shows that our app returns with 0 when we click "Continue") which is fine.

However for MacOS 26 though, it seems that TestFlight just directly SIGKILLs our app (indicated by lldb), which means that all of our app's child processes are left orphaned. Even worse, our app is singleton, which means that when the app relaunches it fails, because the leftover child processes from the previously SIGKILLed session is still alive, and even if we want to kill those orphaned child processes we can't because our app is sandboxed thus cannot kill processes outside of the current sandbox.

We captured output from log stream (app name redacted):

12-02 22:08:16.477036-0800 0x5452     Default     0x5a4a7              677    7    installcoordinationd: [com.apple.installcoordination:daemon]  -[IXSCoordinatorProgress setTotalUnitsCompleted:]: Progress for coordinator: [com.our.app/Invalid/[user-defined//Applications/OurApp.app]], Phase: IXCoordinatorProgressPhaseLoading, Percentage: 99.454 123: Attempt to set units completed on finished progress: 214095161 2025-12-02 22:08:16.483056-0800 0x53ba     Default     0x5a5c9              167    0    runningboardd: (RunningBoard) [com.apple.runningboard:connection] Received termination request from [osservice<com.apple.installcoordinationd(274)>:677] on <RBSProcessPredicate <RBSProcessBundleIdentifierPredicate "com.our.app">> with context <RBSTerminateContext| explanation:installcoordinationd app:[com.our.app/Invalid/[user-defined//Applications/OurApp.app]] uuid:A3BC0629-124E-4165-ABB7-1324380FC354 isPlaceholder:N re portType:None maxTerminationResistance:Absolute attrs:[ 2025-12-02 22:08:16.488651-0800 0x53ba     Default     0x5a5c9              167    7    runningboardd: (RunningBoard) [com.apple.runningboard:ttl] Acquiring assertion targeting system from originator [osservice<com.apple.installcoordinationd(274)>:677] with description <RBSAssertionDescriptor| "installcoordinationd app:[com.our.app/Invalid/[user-defined//Applications/OurApp.app]] uuid:A3BC0629-124E-4165-ABB7-1324380FC354 isPlaceholder:N" ID:167-677-1463 target:system attributes:[ 2025-12-02 22:08:16.489353-0800 0x53ba     Default     0x5a5c9              167    0    runningboardd: (RunningBoard) [com.apple.runningboard:process] [app<application.com.our.app.485547.485561(501)>:2470] Terminating with context: <RBSTerminateContext| explanation:installcoordinationd app:[com.our.app/Invalid/[user-defined//Applications/OurApp.app]] uuid:A3BC0629-124E-4165-ABB7-1324380FC354 isPlaceholder:N reportType:None maxTerminationResistance:Absolute attrs:[ 2025-12-02 22:10:23.920869-0800 0x5a5a     Default     0x5a4c6              674    14   appstoreagent: [com.apple.appstored:Library] [A95D57D7] Completed with 1 result: <ASDApp: 0xc932a8780>: {bundleID = com.our.app; completedUnitCount = 600; path = /Applications/OurApp.app; installed = 0} 2025-12-02 22:10:32.027304-0800 0x5ae5     Default     0x5a4c7              674    14   appstoreagent: [com.apple.appstored:Library] [BEB5F2FD] Completed with 1 result: <ASDApp: 0xc932a8780>: {bundleID = com.our.app; completedUnitCount = 600; path = /Applications/OurApp.app; installed = 0} 2025-12-02 22:10:36.542321-0800 0x5b81     Default     0x5a4c8              674    14   appstoreagent: [com.apple.appstored:Library] [185B9DD6] Completed with 1 result: <ASDApp: 0xc932a8780>: {bundleID = com.our.app; completedUnitCount = 600; path = /Applications/OurApp.app; installed = 0}

The line "Terminating with context" seems suspicious. This line is not seen on MacOS 15, only MacOS 26. Is this documented behavior? If so, how can we handle this?

Sorry for the format. I'll repost the logs here:

I'd need to dig through our code to be sure of all the details; however, this is what's mostly likely going on:

Now this is where things go wrong. On macOS 15, our app seems to be gracefully terminating (we attached it with lldb, and it shows that our app returns with 0 when we click "Continue“), which is fine.

This is likely the "classic" macOS app lifecycle, which involves your app receiving an AppleEvent and then "choosing" to exit. Strictly speaking, your app wasn't terminated— the system asked it to quit, and it eventually "chose" to exit (it wasn't not required to).

However, for macOS 26, though, it seems that TestFlight just directly SIGKILLs our app.

This sounds like the sudden app termination lifecycle, as described here. In that approach, your app is responsible for "telling" the system when it can't be killed, and the system is allowed to terminate your app any other time. The link above has the full details on that.

However...

(indicated by lldb), which means that all of our app's child processes are left orphaned. Even worse, our app is a singleton, which means that when the app relaunches, it fails because the leftover child processes from the previously SIGKILLed session are still alive, and even if we want to kill those orphaned child processes, we can't because our app is sandboxed and thus cannot kill processes outside of the current sandbox.

...this is an edge case you still need to sort out and fix. The user can put you into exactly the same state using "Force Quit" as will crashing. The standard solution here is for the child to monitor the parent and self-terminate if the parent dies.

Also, on this point:

Even worse, our app is a singleton, which means that when the app relaunches, it fails.

How/why is this failure happening? Keep in mind that macOS is a multiuser system, so using system-wide state for this sort of thing can create a situation where your app appears to fail randomly based on the state of other users who happen to have left your app running.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

I agree that we need to handle scenarios such as force quit or unexpected termination, and we are improving those cases. However, I would still like to clarify the behavior specifically during app updates.

on macOS 15 the app terminates cleanly, but on macOS 26 it is immediately killed without entering any shutdown flow. We would like to understand whether this change aligns with the AppleEvent-based quit model you mentioned.

Specifically:

  1. What is AppleEvent ? Is a quit AppleEvent still sent when TestFlight replaces the app during an update?

  2. If so, how can an Electron app observe or respond to it?

  3. Has the expected termination behavior changed across recent macOS versions?

We also opened an issue on the Electron side for reference: https://github.com/electron/electron/issues/49132

Thank you.

I agree that we need to handle scenarios such as force quit or unexpected termination, and we are improving those cases. However, I would still like to clarify the behavior specifically during app updates. On macOS 15, the app terminates cleanly, but on macOS 26, it is immediately killed without entering any shutdown flow. We would like to understand whether this change aligns with the AppleEvent-based quit model you mentioned. Specifically:

What is AppleEvent?

AppleEvents are one of the mechanisms the system uses to deliver "actions" into your app. As a concrete example, take this sequence:

  1. Right-click on an app’s dock icon.

  2. A menu appears.

  3. Select "Quit".

  4. App quits.

The menu presented in #2 is part of the Dock, NOT your app, which means your app didn't have any direct involvement with presenting that menu. The way it "found out" it was supposed to "Quit" was that it received the "quit" AppleEvent. The event system itself isn't all that well documented because most apps don't actually interact with it all that much. For example, AppKit automatically routes the "quit" AppleEvent to "applicationShouldTerminate:", which means most apps use exactly the same code path for both "Quit" menu item and the "quit" AppleEvent, even though those are totally different code paths.

If you want to learn more about it, the "Apple Events Programming Guide" is probably the most complete resource. Two things to understand if you want to look into this more:

  • The AppleEvent system was introduced in 1993 as part of System 7 (Classic MacOS), making it one of the oldest components in the entire system. Only Mach (the core of the kernel) can really be considered "older", but the kernel has changed FAR more than the AppleEvent system has.

  • Much of the AppleEvent documentation is entangled with AppleScript documentation. That's because AppleScript was actually "built" as a high-level language for stringing together sequences of AppleEvents. That's also why "all" apps provide a minimal amount of scriptability (like "open" and "quit") - AppleEvents are such a core part of the system app infrastructure that a process that doesn't respond to the core AppleEvent set ISN'T actually "an app".

I really want to emphasize that last point. AppleEvents aren't "optional", they're a core part of our entire app architecture. Your app is ALREADY using them, because that's just how the system works. Electron is hiding them from you (just like AppKit does), but that doesn't mean they're not there.

That leads to here:

Is a quit AppleEvent still sent when TestFlight replaces the app during an update?

I'm not sure. I looked into this a bit more, and there was a bug in our beta seeds (r.151635186) where an ordering issue meant that we were trying to send "quit" but we ended up terminating before that could have properly acted on. That bug was fixed before macOS 26 shipped. However, the fact that you're getting "SIGKILL" would indicate that it might be something like this, since sudden termination would have used "SIGTERM".

If you haven't already, please file a bug on this and then post the bug number back here. As part of that bug, please upload a sysdiagnose taken shortly after you've replicated that problem.

If so, how can an Electron app observe or respond to it?

Your app already is, as it can't really function without it. However, you can confirm this yourself by using the Dock flow above.

Has the expected termination behavior changed across recent macOS versions?

No, not really. Sudden termination is the only major change we've made but it's obscure, not "new".

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

I see, so "quit" AppleEvent is kinda like the Windows WM_QUIT message. In that case Electron should already handle that.

We definitely will file a bug. In the meantime, the documentataion for Sudden Termination says it's for shutdown. Does Testflight also use it? If so, what is the default value for NSSupportsSuddenTermination if not set in Info.plist? Should we manually set that value to false in our Info.plist?

Even worse, our app is a singleton, which means that when the app relaunches, it fails.

This is a bit of bad wording on me. To clarify, it's not the app failing to launch, but that our program expects to launch its child processes then communicate with them using domain sockets, but if leftover orphans from previous sessions were present, then we can't open new domain sockets on the same path because the old one is still there. To be pedantic we could try opening on another path, but we haven't implemented that. Currently if this happens then our program shows error and exit. This is what I mean "fails".

The user can put you into exactly the same state using "Force Quit" as will crashing. The standard solution here is for the child to monitor the parent and self-terminate if the parent dies.

We already implemented that. The problem is that, in the case of user force-quitting, the user inflicted that upon themselves so the "the app quit unexpectedly" message is nothing out of ordinary, but if a simple app update also prompts "the app quit unexpectedly" that doesn't sound very good.

First off, one other question I should have asked earlier— what does the crash log you're getting from the SIGKILL actually show? Is it just "random" state (whatever your app happened to be doing at the time)? Or does it show a "pattern", particularly anything tied to your app’s own quit cycle?

One possibility that I didn't cover here is that your app IS getting 'quit', but something is going wrong and stalling things long enough that the system kills you.

I see, so "quit" AppleEvent is kinda like the Windows WM_QUIT message.

Yes, except WM_QUIT is like the 'quit', since the AppleEvent came first. Yes, AppleEvents are that old.

We definitely will file a bug.

Bug number?

In the meantime, the documentation for Sudden Termination says it's for shutdown.

Unfortunately, the phrasing in our documentation is using trickier wording that it really should. Here's what it actually says:

"macOS 10.6 and later includes a mechanism that allows the system to log out or shut down more quickly by, whenever possible, killing applications instead of requesting that they quit themselves."

Strictly speaking, it's saying "this is why we did it", not "this is the only time we ever use this mechanism". The documentation is also fairly old* and our use of these mechanisms tends to "spread" over time.

*FYI, phrasing like "macOS 10.6 and later" generally indicates that the documentation was written at or near that particular version number.

Does TestFlight also use it?

Just to clarify, the fact you're getting SIGKILL means this isn't being caused by sudden termination. I'm not sure if it specifically does or not (the code supports all of our platforms, which makes it tricky to trace) but, having looked at this a bit more, I'm fairly sure sudden termination isn't a factor.

Having said that...

If so, what is the default value for NSSupportsSuddenTermination if not set in Info.plist? Should we manually set that value to false in our Info.plist?

My general guidance is that if you specifically want/don't want something to happen, then you should set the key yourself. It's much easier for a default behavior to change vs. changing the behavior of a defined key.

Even worse, our app is a singleton, which means that when the app relaunches, it fails. This is a bit of bad wording on my part. To clarify, it's not the app failing to launch, but that our program expects to launch its child processes then communicate with them using domain sockets, but if leftover orphans from previous sessions were present, then we can't open new domain sockets on the same path because the old one is still there. To be pedantic, we could try opening on another path, but we haven't implemented that. Currently, if this happens, then our program shows an error and exits. This is what I mean "fails".

Yes, that's basically the kind of thing I expected. My original point still stands— it's always going to be possible for your app to get into this state, so your app should have a plan for getting "out" of it. Note that this isn't just about launching new helpers, it's also about getting the old ones to clean themselves up. That leads to here:

We already implemented that.

If your children are self-terminating, then why is that interfering with your relaunch?

Or is the concern here just the "quit unexpectedly" dialog:

The problem is that, in the case of user force-quitting, the user inflicted that upon themselves, so the "the app quit unexpectedly" message is nothing out of the ordinary, but if a simple app update also prompts "the app quit unexpectedly," that doesn't sound very good.

...which, admittedly, is a problem/issue.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

MacOS 26 TestFlight SIGKILLs app when updating
 
 
Q