Category Archives: Hacking

Purgeable Mac Apps

For months now, I have been scratching my head over a small but persistent number of “crash reports” affecting a few of my apps. The issue is most prevalent in MarsEdit, where I have a handful of users who run into the issue multiple times per day. Luckily, one of these users is my good friend and colleague, Manton Reece. I’ve been peppering him with questions about the issue for weeks, while he stoicly puts up with the behavior.

Even with the assistance of a highly technical friend who can reproduce the issue at will, I had thrown my arms up in despair several times. I put “crash reports” in quotes above, because although my in-app crash reporter notices the app abruptly terminates, the system doesn’t create any obvious artifacts. No crash or hang reports. No “Quit Unexpectedly” dialog. The app is just … gone. I wrote a question in the Apple Developer Forums, which turned into a kind of de facto diary as I pursued the issue.

When I started to feel bad about asking Manton to try this, that, and the other thing, I finally asked if he could send me a “sysdiagnose” report. If you’re curious, the easiest way to grab one of these on any Mac is to simply press the Control, Option, Command, Shift, and “.” (period) keys at once. You’ll see the screen flash, an indication the system is starting to collect the reports. A few minutes later the report will be revealed in the Finder: a probably quite large zip archive. Open it up and see the wealth of information about nearly every aspect of the system.

Yet even with this wealth of information, I was stymied. It wasn’t until I chanced upon the delightfully pertinent nuggets of information in “/var/log/com.apple.xpc.launchd/launchd.log” that I got my first whiff of a clue:

2022-05-03 09:15:22.088718 (gui/501/application.com.red-sweater.marsedit4.384452971.384452977 [13840]) : exited with exit reason (namespace: 15 code: 0xbaddd15c) - OS_REASON_RUNNINGBOARD | <RBSTerminateContext| code:0xBADDD15C explanation:CacheDeleteAppContainerCaches requesting termination assertion for com.red-sweater.marsedit4

Here we have a message asserting that MarsEdit was terminated, on purpose, and better still, it includes an explanation! As far as explanations go, “CacheDeleteAppContainerCaches” is not much of one, but it did give me something to go on. Searching for the term yielded pertinent results like this post about Apple Mail and Safari “suddenly quitting.” Unfortunately, they all seem to be scratching their heads as much as I am.

The other thing that jumped out at me from the log was the term “OS_REASON_RUNNINGBOARD”. Searching for this results in only a few scant links, all related to Apple’s open source Darwin kernel. However, Searching instead for just “RunnningBoard” offered a glimmer of hope. A post on Howard Oakley’s blog, “RunningBoard: a new subsystem in Catalina to detect errors“, includes a particularly succinct description of the eponymous OS subsystem (emphasis mine):

Catalina brings several new internal features, a few of which have been documented, but others seem to have slipped past silently. Among the latter is an active subsystem to replace an old service assertiond, which can cause apps to unexpectedly terminate – to you and me, crash – in both macOS 10.15 and iOS 13: RunningBoard.

Unexpected termination. Yep. To you and me? Crashing. At this point in the story I’m going to elide several hours of long, tedious, and yet still somehow fun work, wherein I disabled System Integrity Protection on my Mac, so that I could attach to the pertitent system daemons and try to make sense of how, and when, they might decide to unilaterally terminate an app like MarsEdit. While digging deeper into the issue, I remembered that “explanation” from the log, CacheDeleteAppContainerCaches, and it reminded me of system maintenance software like CleanMyMac. I normally shy away from these kinds of apps because they are historically known to be overly-aggressive in what they decide to delete. In the name of science, however, I decided to run it, with care, on my Mac.

Boom! After running CleanMyMac once, MarsEdit, along with Numbers, were suddenly not running anymore. I had finally reproduced the issue on my own Mac for the first time. Anybody who has fixed software bugs, either for a living or as a passion, knows this is the critical first step to really addressing an issue. With some tinkering, I was able to narrow down the reproduction steps to running the “Free Up Purgeable Space” action. It turns out this is invokes a system API responsible for trying to delete caches, etc., from a Mac. Normally the system only does this when disk space is critically low, but CleanMyMac gives you the option to exercise the behavior at any time.

That single log line quoted above turns out to hold another gem of information. The “code:0xBADDD15C” looks like it could be an arbitrary hexadecimal value, but it’s an example of an error code designed to both uniquely identify and suggest a mnemonic clue to the underlying issue. Apple documents many of these codes, which include 0xc00010ff (cool off), 0xdead10cc (deadlock), and 0xbaadca11 (bad call). I searched the system frameworks for this code and found it in the disassembly of “/System/Library/PrivateFrameworks/CacheDelete.framework”. Particularly, in an internal function called “assert_group_cache_deletion”. It was only after exploring the issue in the forums, did Quinn explain that the code in this scenario is a mnemonic for “bad disk”. I guess it was easier to spell out than trying to represent “full disk”.

Equipped with all this new information, what can we do about the unexpected terminations? Well, nothing. I do wish Apple’s framework would try asking nicely if the app would quit, before summarily terminating it, but I guess the thinking is that this functionality should typically only be reached in extenuating circumstances. After learning more about the issue, I confirmed with Manton that his Mac did have low disk space, so I guess it was just the system trying its best to free up space that caused the issue for him.

The one thing I plan and hope to do as followup is to amend my built-in crash reporter so that it will not prompt the user or report a crash when the app terminates for this reason. I think it should be possible to detect the codes alluded to above, and simply let “0xBADDD15C” terminations happen without fanfare.

Reminder Plumbing

I am a fan of The Omni Group’s OmniFocus for both the Mac and iOS. While I’ve owned the apps for a long time I’ve only recently started taking more advantage of them. They have become critical to my own deployment of the Getting Things Done task-management methodology.

One particularly great workflow is afforded by the iOS version of the app’s option to automatically import reminders from the default iPhone reminders database. What this means in practice is you can use Siri to add items to OmniFocus. You say: “add take out the trash to my reminders list,” and the next time you open OmniFocus, the items are instantly imported to OmniFocus and removed from the system list. (Intrigued? You have to make sure you turn on the option in OmniFocus for iOS preferences.)

Unfortunately, OmniFocus for Mac doesn’t support this. I love OmniFocus for both Mac and iOS, but it turns out that because I lean so heavily on using Siri to add items, I tend not to open OmniFocus while I’m on the go. When I come home and get to work on my Mac, I notice that OmniFocus doesn’t contain any of my recently added items, so I have to go through the cumbersome steps of opening my iPhone and launching OmniFocus just to get this theoretically time-saving trick to work right.

I’m looking forward to a future release of OmniFocus that supports a similar mechanism for automatically importing reminders. Who knows, maybe the feature will even make its way into the forthcoming OmniFocus 2.0. But I decided I don’t want to wait even a single day longer for this functionality, so I decided to tackle the problem myself.

I developed a tool, RemindersImport, that solves the problem by adding behavior to my Mac that strongly emulates the behavior built in to OmniFocus for iOS. When launched, the tool will scan for non-location-based reminders, add them to OmniFocus (with start and due dates in-tact!), and then remove them from Apple’s reminders list.

If this sounds as fantastic to you as it does to me, I invite you to share in the wealth of this tool:

Click to download RemindersImport 1.0b3.

How To Use It

Warning: RemindersImport is designed to scan your Mac OS X Reminders and remove them from the default location in your Reminders list so that they may be added instead to OmniFocus. You should understand very well that this is what you want to do before running the tool.

Let’s say you have 5 Reminders that you added via Siri on your phone. In the background, thanks to Apple’s aggressive syncing, these have been migrated over to your Mac and are now visible in Reminders.app. To migrate these from Reminders to OmniFocus, just run the tool once:

./RemindersImport

If you’ve opted to use a different reminders list for OmniFocus, you can specify the name on the command line to import from that list instead:

./RemindersImport "Junk to Do"

Of course, running the tool by hand is about as annoying as having to remember to open up the iPhone and launch OmniFocus, so ideally you’ll want to set this thing up to run on its own automatically. I haven’t yet settled on the ideal approach for this, but a crude way of setting it up would be to just use Mac OS X’s built-in cron scheduling service to run the tool very often, say every minute:

*/1 * * * * /Users/daniel/bin/RemindersImport > /dev/null 2>&1

(Note: to edit your personal crontab on Mac OS X, just type “crontab -e” from the Terminal. Then paste in a line like above and change the path to match your own storage of the tool)

Something I’d like to look into is whether it would make sense to set this tool up as lightweight daemon that just stays running all the time, waiting for Reminders database changes to happen, and then snagging stuff. For now, the crontab based trick is doing the job well enough for my needs.

How To Leverage It

I am sharing the source code for the tool under the liberal terms of the MIT License. You can download the source code on GitHub, and of course I would also welcome pull requests if you make meaningful improvements to the code.

The RemindersImport tool satisfied my needs for automatic OmniFocus import from a single list. Maybe your needs are more complicated: you only want to import tasks that meet certain criteria, or you want to import, but leave the existing items in Reminders. Or you want to do something similar with a completely different app than OmniFocus.

It should also go without saying that the general structure of the code serves as a working model for how you might implement an “import from reminders” type of feature in your own apps. Since I learned about the OmniFocus for iOS trick, it’s always jumping out at me when another app could benefit from applying the same technique.

For example, imagine if the Amazon app offered a feature to import as any items from a list called “Amazon”. Then I could, in the middle of a run, ask Siri to “add running shoes to my amazon list,” and be assured that it would find its way to the right place.

Since Siri first debuted as a system-level feature of iOS, developers have been yearning for “a Siri API.” In the absence of that, this is as good as it gets. This “reminder plumbing” is available to every app but has so far been woefully under-utilized. Maybe once you play around with how well it works with OmniFocus, you’ll get inspired to add something to your own apps, or to beg for similar functionality from the developers of apps you love. When you do, I hope my contributions provide you with a head-start.