My macOS app is getting closed by the system

Hi,

I've been trying to resolve an issue that my users are facing for about one year, but I haven't been able to so far. That's why I'm turning to you all for some ideas.

Some of my users have noticed that my app suddenly exits. It runs in the background as a menu bar app, so when they go to use it, they realize it's no longer running.

I've checked Crashlytics and asked users to check their Console app for crash reports, but there are none. The conclusion so far is that it's not a crash, but a silent termination.

I haven't experienced this on my own machine, which makes it incredibly difficult to debug or identify the cause.

Recently, I thought I'd pinned down the problem. My app was declaring:

	<key>NSSupportsSuddenTermination</key>
	<true/>

Based on the documentation, this is intended to quickly terminate the app during logout or system shutdown, but I read it can also be triggered when the system needs resources. It seemed like the perfect root cause. However, even after turning it off, one of my users is still experiencing the problem. I'm officially running out of ideas.

Does anyone have suggestions on what else I should check?

My app currently declares:

	<key>LSUIElement</key>
	<true/>
	<key>NSSupportsAutomaticTermination</key>
	<false/>
	<key>NSSupportsSuddenTermination</key>
	<false/>
Answered by DTS Engineer in 887802022

Based on the documentation, this is intended to quickly terminate the app during logout or system shutdown.

I'm not sure why we wrote it that way, but I believe that description is somewhat misleading. What "sudden termination" actually does is change how an app is expected to handle quit. The standard quit "flow" is that an app receives kAEQuitApplication, which then lets in opt in/out of exiting through applicationShouldTerminate().

Sudden termination reverses that approach. When sudden termination is enabled, the system "quits" your app by directly terminating it without any notice or warning. Your app is then responsible for telling the system NOT to do that by using disableSuddenTermination()/enableSuddenTermination(). You can read more about it here, but my guess is that it's probably not a factor. That's primarily because it's hard to send kAEQuitApplication to an FBA (Faceless Background App) unless you really dig into the system, but also because I believe we start with sudden termination disabled, so your app need to enable it once before it actually comes into effect.

Next, a quick comment here:

I read it can also be triggered when the system needs resources.

Sort of but not really. Sudden termination itself is a direct replacement for "Quit..." and wouldn't be triggering "on its own". The system itself will terminate processes under high enough memory load, however, that process is fairly obvious (for example, the warning message the system posts telling you to quit apps) and, more to the point, an FBA's resource usage is typically low enough that they tend to be ignored.

I've checked Crashlytics and asked users to check their Console app for crash reports, but there are none. The conclusion so far is that it's not a crash, but a silent termination.

If you're in direct communication with a user, then the best thing you can get is a sysdiagnose that was triggered shortly after the problem occurred (the exact time doesn't matter very much, as long as the machine hasn't been rebooted and it's not "hours" later). Interpreting the console log can be difficult, but it will tell you what happened if you look hard enough.

Having said that...

I've been trying to resolve an issue that my users are facing for about one year, but I haven't been able to so far. That's why I'm turning to you all for some ideas.

...my own recommendation would be to solve the problem by having the system relaunch your app. If you're using SMAppService and you’re configured as an agent, then you can use the "KeepAlive" key in your launchd plist to have the system relaunch your FBA anytime it exists for any reason. See "man lauchd.plist" for the full details. On the other hand, if you're still configured as a login item... well, I would fix that and convert to an agent. Anticipating your questions:

  • Is there some advantage to being a login item vs. an agent?

No. Technically I believe they show up in different places inside the Settings.app UI, but there isn't any fundamental difference between how the two types run or what they can do.

  • Can you think of ANY reason to use a Login Item over a LaunchAgent?

No, not really?

The enablement flow is a bit different for a LaunchAgent, but that's a one-time operation and LaunchAgents are FAR more powerful than Login Items. LaunchAgents can install for "all" users (not just the current user) which is different than LoginItems, but that can be controlled by the user and is what most apps would prefer anyway.

  • Are you saying that macOS has two entirely different mechanisms that basically do the same thing?

Yes.

  • Why?

History.

In the beginning, launchd did not exist at all, and the system had two different problems it needed to solve:

  1. Classic MacOS had a way to launch apps when the user logged in, and users liked it, so MacOS X had to do it too. So "Login Items" were created.

  2. The system needed some way to manage all of the daemons it needed to launch, so "StartupItems" were created. They were okay but not great[1], so in MacOS 10.4, launchd was created, which is when the concept of "Daemons and Agents" was introduced.

Up until macOS 13 and SMAppService, the key difference between those two approaches was that agents were configured via a plist file, and the plist file had to embed the specific path that plist would manage, while "Login Items" were "app" targets which the system would try to track down if/when the app bundled moved. LaunchAgents were always more powerful/configurable than "Login Items," but the hard-coded path limited their usefulness, as did the lack of API support for installing them. However, SMAppService removes that issue.

  • Why haven't Login Items been removed entirely?

Mostly because it would be more trouble than it was worth. The original purpose (giving the user a way to pick the apps they want to launch at login) still exists, so we still need to implement that functionality. Under the hood, they're all being managed by the same infrastructure, so supporting them isn't really any more “work," and removing them would mean we'd have to force all developers to the "new" (but better...) agent architecture. All of that would be far more trouble than it was worth.

[1] In my experience, the more involved you were with boot process in general and StartupItems in particular, the lower your opinion of them was.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Based on the documentation, this is intended to quickly terminate the app during logout or system shutdown.

I'm not sure why we wrote it that way, but I believe that description is somewhat misleading. What "sudden termination" actually does is change how an app is expected to handle quit. The standard quit "flow" is that an app receives kAEQuitApplication, which then lets in opt in/out of exiting through applicationShouldTerminate().

Sudden termination reverses that approach. When sudden termination is enabled, the system "quits" your app by directly terminating it without any notice or warning. Your app is then responsible for telling the system NOT to do that by using disableSuddenTermination()/enableSuddenTermination(). You can read more about it here, but my guess is that it's probably not a factor. That's primarily because it's hard to send kAEQuitApplication to an FBA (Faceless Background App) unless you really dig into the system, but also because I believe we start with sudden termination disabled, so your app need to enable it once before it actually comes into effect.

Next, a quick comment here:

I read it can also be triggered when the system needs resources.

Sort of but not really. Sudden termination itself is a direct replacement for "Quit..." and wouldn't be triggering "on its own". The system itself will terminate processes under high enough memory load, however, that process is fairly obvious (for example, the warning message the system posts telling you to quit apps) and, more to the point, an FBA's resource usage is typically low enough that they tend to be ignored.

I've checked Crashlytics and asked users to check their Console app for crash reports, but there are none. The conclusion so far is that it's not a crash, but a silent termination.

If you're in direct communication with a user, then the best thing you can get is a sysdiagnose that was triggered shortly after the problem occurred (the exact time doesn't matter very much, as long as the machine hasn't been rebooted and it's not "hours" later). Interpreting the console log can be difficult, but it will tell you what happened if you look hard enough.

Having said that...

I've been trying to resolve an issue that my users are facing for about one year, but I haven't been able to so far. That's why I'm turning to you all for some ideas.

...my own recommendation would be to solve the problem by having the system relaunch your app. If you're using SMAppService and you’re configured as an agent, then you can use the "KeepAlive" key in your launchd plist to have the system relaunch your FBA anytime it exists for any reason. See "man lauchd.plist" for the full details. On the other hand, if you're still configured as a login item... well, I would fix that and convert to an agent. Anticipating your questions:

  • Is there some advantage to being a login item vs. an agent?

No. Technically I believe they show up in different places inside the Settings.app UI, but there isn't any fundamental difference between how the two types run or what they can do.

  • Can you think of ANY reason to use a Login Item over a LaunchAgent?

No, not really?

The enablement flow is a bit different for a LaunchAgent, but that's a one-time operation and LaunchAgents are FAR more powerful than Login Items. LaunchAgents can install for "all" users (not just the current user) which is different than LoginItems, but that can be controlled by the user and is what most apps would prefer anyway.

  • Are you saying that macOS has two entirely different mechanisms that basically do the same thing?

Yes.

  • Why?

History.

In the beginning, launchd did not exist at all, and the system had two different problems it needed to solve:

  1. Classic MacOS had a way to launch apps when the user logged in, and users liked it, so MacOS X had to do it too. So "Login Items" were created.

  2. The system needed some way to manage all of the daemons it needed to launch, so "StartupItems" were created. They were okay but not great[1], so in MacOS 10.4, launchd was created, which is when the concept of "Daemons and Agents" was introduced.

Up until macOS 13 and SMAppService, the key difference between those two approaches was that agents were configured via a plist file, and the plist file had to embed the specific path that plist would manage, while "Login Items" were "app" targets which the system would try to track down if/when the app bundled moved. LaunchAgents were always more powerful/configurable than "Login Items," but the hard-coded path limited their usefulness, as did the lack of API support for installing them. However, SMAppService removes that issue.

  • Why haven't Login Items been removed entirely?

Mostly because it would be more trouble than it was worth. The original purpose (giving the user a way to pick the apps they want to launch at login) still exists, so we still need to implement that functionality. Under the hood, they're all being managed by the same infrastructure, so supporting them isn't really any more “work," and removing them would mean we'd have to force all developers to the "new" (but better...) agent architecture. All of that would be far more trouble than it was worth.

[1] In my experience, the more involved you were with boot process in general and StartupItems in particular, the lower your opinion of them was.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

I finally managed to collect logs and pin down the issue:

2026-06-06 19:40:48.660726+0200 0x2ad59d   Default     0x0                  1
0    launchd:
[gui/502/application.com.bundle.id.198150604.198151841 [89659]:]
exited with exit reason (namespace: 15 code: 0xbaddd15c) -
OS_REASON_RUNNINGBOARD | <RBSTerminateContext| code:0xBADDD15C
explanation:CacheDeleteAppContainerCaches requesting termination assertion for
com.bundle.id reportType:None
maxTerminationResistance:NonInteractive attrs:[ <RBSPreventLaunchLimitation|
<RBSProcessPredicate <RBSProcessBundleIdentifierPredicate
"com.bundle.id">> allow:(null)>

2026-06-06 19:40:48.660738+0200 0x2ad59d   Default     0x0                  1
0    launchd:
[gui/502/application.com.bundle.id.198150604.198151841 [89659]:]
service state: exited

2026-06-06 19:40:48.660756+0200 0x2ad59d   Default     0x0                  1
0    launchd:
[gui/502/application.com.bundle.id.198150604.198151841 [89659]:]
internal event: EXITED, code = 0

As it turns out, the problem appears when the system is running low on disk space. Then the runningboardd kills the app to perform cache clean up:

OS_REASON_RUNNINGBOARD | <RBSTerminateContext| code:0xBADDD15C
explanation:CacheDeleteAppContainerCaches 

Is there any way to prevent that? My application is not using cache at all besides what Apple frameworks and common 3rd party SDKs use.

As it turns out, the problem appears when the system is running low on disk space.

So, this is another example of why this was my recommendation above:

"...my own recommendation would be to solve the problem by having the system relaunch your app."

The underlying issue here is that:

  1. It's surprisingly difficult to know/predict ALL of the different ways/reasons the system might terminate your app.

  2. Some of those reasons aren't errors or "problems", but are simply part of the system’s normal behavior.

...case in point, I didn't think of cache delete when I wrote my reply above but, yes, this is how it works and it's been doing this for a fairly long time. More importantly, it's just one example of this, as app updates work exactly the same way.

That leads to here:

Is there any way to prevent that? My application is not using cache at all besides what Apple frameworks and common 3rd party SDKs use.

Sure. Cache delete only terminates apps when it actually has data to delete, so if you don't store any data in one of our temporary directories, we won't terminate your app. However, in practice that advice isn't all that useful— many SDKs and some of our frameworks automatically use those directories, so making this work means actively auditing and tweaking your implementation to avoid using these directories. That's also an ongoing process, since our behavior might change due to any number of factors (API updates, app usage patterns, etc.). More importantly, having done all that work... there's no guarantee that your app won't STILL be terminated due to some completely unrelated mechanism (like app updates). Finally, all of this can change over time, so what works "now" can end up breaking in the future.

You can spend your time trying to design/predict/avoid all of these issues... or you can have the system relaunch your app and let the issue "solve itself" and not worry about it again.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Thank you for your quick reply! I’m sure that LaunchAgent is a great workaround for this issue, but it’s a workaround. I wonder if there is any reliable way to fix that issue. My app could be relaunched with no issues, but what if the app is doing some work, or has some unsaved progress, or for any other reason we don’t want the app to be randomly killed and relaunched every couple of minutes?

Usually, we don’t want to fix app crashes by installing an agent relaunching the app. The same way we don’t want to fix app getting killed by the system by installing that agent. It would be a good solution if we want to make sure that the app is always running no matter what happens, but it’s not a good solution if we just want to make sure that the app operates normally as the user expects it to work.

Also, this workaround works only if the user wants to launch the app on every system startup. If the user doesn’t select that option, the workaround is not applicable and the app will be terminated randomly.

Also, setting KeepAlive = true means that the app will be relaunched even if the user decides to quit the app manually, which isn’t a desired behavior. Probably I could intercept a manual termination and update LaunchAgent, but it seems like building a serious logic just to workaround an issue that shouldn’t occur in the first place.

I’m quite sure that my app isn’t heavily using cache, so I’d love to know how to diagnose this problem and how to determine why on my client’s device, only my app is affected by this aggressive clean up macOS behavior.

I think it shouldn’t be considered normal that the app might be killed every couple of minutes just because the user has let’s say 8gb free disk space instead of 30gb.

I've just got more details from my customer:

  • The app's cache is using 12kb, so it's not normal that the system kills the app for that reason.
  • The customer has 9GB of free disk space + iCloud space that could be used by the system by moving local files to cloud.
My macOS app is getting closed by the system
 
 
Q