The macOS Sequoia 15.8 updater stole my RAM
(it’s true, it confessed! 😉)
I run a bunch of VMs for things like Claude, and for compatibility testing (sadly I feel compelled to test on macOS Tahoe despite the dumpster fire it is, so thank goodness it runs in a VM on Sequoia).
Yesterday I tried doing the 15.8 update (from 15.7.9) inside one of those VMs. It hanged, midway through the post-reboot install process. I had to ‘power-cycle’ the VM. Which booted it back into 15.7.9, except… shockingly slow. Like, I can watch the individual elements of the GUI render like it’s the 1980s again.
I eventually figured out that my 16 GiB VM could only “see” 1.75 GiB of RAM, and was therefore swapping all to hell.
Which unintentionally answers an interesting trivia question: can macOS still boot in just 1.75 GiB of RAM? Yes, technically. But you’d never want to. And only sometimes – notably, it cannot boot the recovery partition nor the 15.8 updater in 1.75 GiB of RAM – both just hang early in boot – which is just awesome as it meant the obvious ‘recovery’ path – retrying the installer, or even just reinstalling the OS entirely, isn’t an option.
I was at a loss. I could identify only two highly unpalatable solutions:
- Create a new VM from scratch.
- Set fire to my computer and become a cave hermit.
Tempting as one of those was, I turned to Claude instead. I have mixed feelings about “AI”, as I’ll perhaps detail in a later post, but I must admit frontier models like Claude Opus & Fable can do things that were practically impossible even just a year ago, and often beyond my ken.
It took Claude Opus 5 all of about fifteen minutes to figure out the problem and the solution, with only a few Q&As with me. It initially thought this was a balloon driver thing – I don’t know why, that doesn’t seem likely or intuitive – but it at least suggested the key debugging step:
vm_stat | grep -i wired # pages are 16 KiB
sudo zprint | less # per-kext "wired memory" table is near the endzprint revealed that 14,942,224KB (~14.25 GiB) of RAM was wired for VM_KERN_COUNT_BOOT_STOLEN. That’s set here to the value of booter_size which in turn is set here to the result of ml_get_booter_memory_size which is set to the value of the memSizeActual boot arg (or, if that’s not set, the memSize boot arg rounded up to the nearest power of two greater than or equal to 512 MiB) minus memSize. I can’t find anywhere in the xnu source code where those boot args are actually initialised, but in any case they come from ‘NVRAM’ (which on some machines may just be a designated spot on the EFI partition on disk, I think?).
In hindsight, the simplest solution might be – I have not tested it – to just run:
sudo nvram -d boot-argsThat should clear the boot args, eliminating whatever insane stupidity Apple’s updater enacted there. Assuming the nefarious memSizeActual or memSize setting(s) were there, and not dynamically inserted by the boot loader (Claude thinks it’s the latter, which admittedly does seem supported by the resolution that provably does work, so 🤷♂️).
😡 I actually suspected an NVRAM / boot-arg problem pretty quickly, because this isn’t my first rodeo (Firewire debugging of kexts on PowerMacs, woot!). But a quick Kagi search seemed to show – in both its AI summary and the top search results – that there’s no NVRAM on Arm Macs and the concept doesn’t apply. The former is maybe true, but the latter is patently false. Good job, internet.
What Claude suggested was a little more obtuse but did the job: revert mBoot to the working version (18000.161.10) from the broken one (20457.1.29):
cd /path/to/VM.utm/Data/
cp AuxiliaryStorage{,.bak}
dd if=AuxiliaryStorage.bak of=AuxiliaryStorage bs=16384 skip=$((0x224000/16384)) seek=$((0x24000/16384)) count=$((0x200000/16384)) conv=notrunc
strings -a -t x AuxiliaryStorage | grep -E '[im]Boot-[0-9]' # both slots should now show mBoot-18000.161.10⚠️ The magic offset values there (0x224000 & 0x24000) are based on my specific AuxiliaryStorage file for my VM – they may differ from yours. In my case, I had:
$ xxd -s 0x4000 -l 16 AuxiliaryStorage
00004000: 4855 4641 0100 0000 0300 0000 0000 0200 HUFA............
$ xxd -s 0x5000 -l 16 AuxiliaryStorage
00005000: 4855 4641 0100 0000 0200 0000 0000 2200 HUFA..........".
$ strings -a -t x AuxiliaryStorage | grep -E '[im]Boot-[0-9]'
2401e mBoot-20457.1.29
22401e mBoot-18000.161.10Each 16-byte header contains four little-endian uint32 fields:
- The magic “HUFA” (0x 4855 4641) – or I guess “AFUH” in endian little (Apple Firmware Update Header?).
- A version, which is always 1.
- Upgrade count.
- The slot’s offset from 0x4000.
So in my case the base offsets were 0x20_0000 and 0x22_0000 (remember this is endian little, so numbers read backwards 🤪), to which you add the 0x4000 constant.
And voilà, macOS now boots properly again.
I was able to retry the 15.8 update, and this time it worked. Why it didn’t the first time, I don’t know – why it would ever configure the system to have just 1.75 GiB of RAM, I cannot fathom. Thanks, Apple.
I want to point out that in the absence of our AI overlords like Claude, there’s no way I would ever have figured this out. I would have wasted hours trying, or equivalently hours creating a new VM from scratch (and hours more rebuilding my dev environment inside it).
So kudos, Claude Opus 5. 🍻
P.S. In case anyone’s interested, .
- While in this case it was less than helpful, nonetheless I like Kagi and recommend it readily. And I do make use of its AI summaries fairly often (but love that it won’t bother me with them unless I explicitly but effortlessly ask, by appending a ‘?’ to my query). But its AI summariser is only as good as its inputs – GIGO – and in this case the internet is full of half-truths and confused misunderstandings about how Arm Macs boot. 😔