← Blog Building Luminair 24 September 2026 9 min read

Still shipping an Intel build

macOS 27 runs only on Apple silicon. Intel Macs stay on Tahoe 26 and its security updates. Luminair still ships an Intel app, and keeping it honest taught us something about cross-builds: the dangerous failures are the ones that print nothing.

Fig 01  One lockfile, two machines, different installsSchematic · from release.yml and package-lock.json
PACKAGE-LOCK.JSON Both twins listed claude-agent-sdk-darwin-* codex-darwin-* node-pty-darwin-* clipboard-darwin-* esbuild darwin-* Each twin says which cpu it is for. npm picks the one that matches the machine running npm. 6 SEP 2026, MORNING · npm ci Apple silicon build Mac arm64 twins installed x64 twins No error. No warning. Tests pass on arm64. Intel app, packaged no x64 Claude, Codex, node-pty, clipboard PROBE: "no bundled Claude binary found in this bundle" SAME MORNING · npm ci --os darwin --cpu x64 Apple silicon build Mac tests run on arm64 first then x64 twins installed and one file checked by name: claude-agent-sdk-darwin-x64/claude Intel app, packaged x64 engines inside probe starts a query Then signed, notarized, uploaded.
Package names are the five darwin-x64 entries in desktop/package-lock.json (scopes dropped to fit). The before and after are commit 9a6e5ab2, which moved both Mac lanes onto the Apple silicon build Mac, and commit 43c868d7, which fixed the install 39 minutes later.

The short version

  1. Apple said at WWDC 2025 that macOS Tahoe 26 would be the last macOS for Intel Macs. macOS 27 shipped in September 2026 for Apple silicon only.
  2. Luminair still ships an Intel build next to the Apple silicon one. Intel Macs on Tahoe are still real machines doing real work.
  3. To save build minutes we moved the Intel build onto an Apple silicon Mac. npm then installed only the arm64 engine binaries, without any error, and the Intel app came out with no bundled Claude, Codex or terminal.
  4. A post-build probe caught it before release. The fix tells npm which machine the app is for, not which machine is running npm. Weeks later a Windows ARM64 installer failed the same quiet way, for a different reason.
02What Apple did

The last macOS for Intel.

Apple moved the Mac to its own chips in 2020, and the Intel line has been winding down since. In June 2025 the date became concrete. As PetaPixel reported from WWDC, “macOS 26, also known as macOS Tahoe 26, will be the last release that supports Intel-based Macs.”

In April 2026 The FPS Review put it more bluntly: “Apple is ditching support for its Intel-based machines as of next year's OS release.” The Intel Macs that got Tahoe at all were a short list, including the 16-inch MacBook Pro from 2019 and the 27-inch iMac from 2020.

Then it happened. Apple's page macOS 27 Golden Gate is compatible with these computers lists only Apple silicon models and says: “If you have a Mac with Apple silicon, you can upgrade to macOS 27.” On 14 September 2026, The Eclectic Light Company reported the release: it “requires an Apple silicon Mac, including the MacBook Neo, and still includes Rosetta 2”. The same day Tahoe went to 26.7, “starting its first two years of security-only support”.

An operating system can stop moving forward long before the machines under it stop working.
03Why we still build for Intel

Old Macs still write code.

An Intel MacBook Pro from 2019 does not stop compiling when Apple stops shipping it new operating systems. Plenty of developers keep one as a second machine, a build box, or simply their laptop. Luminair runs agents on the Mac you already have, so dropping Intel would mean telling those people to buy a new computer to use a chat window.

So the download page still has two Mac buttons. The site's download script lists an Intel row with the note Older Macs, next to Apple Silicon, and when the browser reports an x86 processor it switches the main Mac button to the Intel file for you.

Keeping a second architecture costs more than a second button, though. Every release has to produce a second signed, notarized app, and every piece of native code inside it has to be the right kind. Luminair bundles real programs, not just JavaScript: the Claude and Codex command-line engines, a terminal library (node-pty), a clipboard helper. Each has a separate build for arm64 and for x64. Put the wrong one in the box and the app opens fine, then fails the moment you ask it to do anything.

04The quiet cross-build

npm did exactly what it was told.

Luminair's release workflow was redesigned on 3 September 2026 around one rule, written at the top of the file: “Intel builds on an Intel runner, Apple Silicon on arm64. No cross-arch force-installs.” GitHub's hosted Intel Mac runner built the Intel app, and GitHub's hosted Apple silicon runner built the other.

Hosted Mac runners are expensive, though. The workflow's own input description says forcing the Mac lanes onto them “costs 10x minutes”. We also had a Mac of our own, registered as a self-hosted runner. So at 06:41 on 6 September, commit 9a6e5ab2 sent both Mac lanes to that Mac when it was online: “arm64 native, x64 via Rosetta; 0 hosted minutes”.

That Mac was Apple silicon. Here is what happened next, step by step:

01

npm installs for the machine it runs on

Packages like the Claude engine ship as one small wrapper plus a set of platform packages, each marked with the operating system and processor it is for. npm installs only the ones that match the machine running npm. On an arm64 Mac, that means the arm64 twins. The x64 twins are skipped, and skipping an optional package is normal, so there is nothing to report.

02

The tests pass

The release gate runs the test suite, a security audit and a check that our own helper binaries are universal. All of it runs on the host's arm64 modules, which are fine. Nothing here is wrong for the machine doing the build.

03

The packager builds an Intel app

electron-builder happily produces an x64 app from that folder. The Electron shell is x64. The engines inside are either arm64 or missing.

04

The probe refuses it

After packaging, a verify step runs a probe inside the finished app that asks the Claude Agent SDK to start one query with the bundled engine. On this build it answered: no bundled Claude binary found in this bundle. The lane failed, and because releases are uploaded to a draft that is only published after every lane verifies, nothing reached the download page.

That probe was added on 1 September for a different bug: in a packaged app, turns on an in-app sign-in had died with “spawn ENOTDIR”. It was written for that failure, and it caught this one.

The fix landed 39 minutes after the change that caused it. It keeps the gate on the host's native modules, then reinstalls the tree for the target machine before packaging:

.github/workflows/release.ymlCross-build prep · lines 353–365
if [ "$(uname -m)" = "arm64" ]; then
  # The gate above ran on the host's native (arm64) modules. The PACKAGED app must carry the x64
  # variants of every platform-specific optional dependency, or the Intel app has no bundled engines.
  npm ci --prefer-offline --no-audit --no-fund --os darwin --cpu x64
  [ -f node_modules/@anthropic-ai/claude-agent-sdk-darwin-x64/claude ] || { echo "::error::x64 Claude engine package not installed"; exit 1; }
else
  echo "native x64 host: nothing to do"
fi

npm's own documentation describes the two flags plainly. --cpu will “Override CPU architecture of native modules to install”, and --os does the same for the operating system. They were always there. We just had not needed them while every build ran on a machine of its own kind.

The verify step also checks the target's Claude engine by path, and the app's main binary and our deskctl helper with lipo, so a wrong slice fails by name. And the release runner's self-heal notes, which map known error lines to an action, list the probe message with the action escalate and zero automatic retries: “if it recurs the workflow regressed”. A retry would only build the same broken app again.

The lesson we tookA build that runs clean on the wrong machine is the worst kind of green. Test the thing you ship, on the terms it will run, after it is packaged. Checking the source tree is not the same as checking the app.
05One lane at a time

Build one Mac, carry the other.

The same morning, at 07:58, commit af33c833 added a mac_arch input to the release workflow with three choices: both, arm64 or x64. It sounds like a small convenience. It exists because the two Macs no longer fail the same way, so we need to be able to rebuild one without touching the other.

Fig 02  What each mac_arch choice buildsFrom release.yml · finalize job
mac_archApple siliconIntel
bothBuilt and verifiedBuilt and verified
arm64Built and verifiedCarried forwardDMG, zip and update feed copied from the current release
x64Carried forwardDMG, zip and update feed copied from the current releaseBuilt and verified
Filled dot: built in this run and checked by name, size and SHA-256. Hollow dot: copied from the latest published release, so every download button and both update feeds keep working. A single-arch run refuses to start if there is no earlier release to carry from.

Carrying forward is not free of checks. A later fix (38ada81c, 7 September) recomputes the carried update feed's digests from the carried bytes, because older feeds were written before the DMG was notarized and stapled, and stapling makes the file slightly larger. The release only goes public when the finalize job has matched every expected file.

The build Mac choice got smarter too. Later on 6 September, our menu-bar release controller learned to send each lane to the Mac that fits it. From its routing code: “arm64 needs an Apple Silicon Mac. x64 goes native to an Intel Mac when one is on, else to any free Mac (Rosetta cross-build).” So the Intel app is built on an Intel Mac whenever one is online, and the cross-build with its reinstall step is the fallback, not the plan.

06The same shape on Windows

An installer that said Completed.

On 24 September we tested an ARM64 Windows installer on a virtual machine. It finished without complaint. The app was not there: no Luminair.exe, no DLLs, no engine binaries. Every ARM64 program in the package, 36 files, had been skipped.

The cause had nothing to do with npm this time. Windows installers built by electron-builder pack the app into a 7-Zip archive that an NSIS plugin unpacks on the user's machine. 7-Zip 24 compresses ARM64 programs with a special ARM64 filter. The NSIS 7z plugin cannot decode that filter, and when it meets a file it cannot read, it skips it without an error.

Fig 03  Two quiet failures, one patternSchematic
STEP THAT SKIPPED WHAT IT SAID THE FIX Mac Intel · 6 Sep npm on an arm64 host skips the x64 packages nothing caught later by the in-app engine probe npm ci --os darwin --cpu x64 Windows ARM64 · 24 Sep NSIS 7z plugin cannot read 7-Zip's ARM64 filter “Completed” 36 files missing on the test machine ELECTRON_BUILDER_7Z_ FILTER=BCJ Both steps did their job as written. Neither one knew what the finished app needed.
Dates and file count from commits 43c868d7 and 40af468d and the comment in desktop/build/beforePack.js.

The fix, in commit 40af468d, sets ELECTRON_BUILDER_7Z_FILTER=BCJ in the pre-pack hook, which makes 7-Zip use the plain filter the plugin can read. The next day, commit 7463aa37 widened it from ARM64 builds to every Windows build, so there is one archive format to reason about.

The same day as the ARM64 discovery, an unsigned test build on Windows 11 had reported “Completed” while antivirus removed every .exe and .dll, a separate cause with the same symptom. Release 1.0.210, shipped that evening, added a guard that catches either cause: the NSIS script now confirms that Luminair.exe, ffmpeg.dll, app.asar and the bundled Claude engine exist after install, and fails with a clear message if they do not. The release workflow runs a real silent install that must keep every bundled binary.

The pattern is the same as on the Mac. A step that skips what it does not understand will never tell you. The only defence is to check the result against a list of what must be there.

07Getting the right build

Which Mac do you have?

  1. 1Open the Apple menu and choose About This Mac. If the chip line says Apple M-something, you want Apple Silicon. If it says Intel, you want Intel.
  2. 2On luminair.io/download, pick the matching row: Apple Silicon or Intel (Older Macs). The main button tries to pick for you from what your browser reports.
  3. 3After that, updates follow the app you installed. Each Mac architecture has its own update feed, and the app picks the feed from the processor it was built for, so an Intel copy is only offered Intel updates.
08What we checked

Checked, and not claimed.

39 min
From the commit that moved Intel onto the Apple silicon Mac (06:41) to the fix (07:20), 6 September 2026
5
darwin-x64 platform packages in the lockfile: Claude, Codex, node-pty, clipboard, esbuild
36
ARM64 files the Windows installer skipped on the test machine, per the fix commit
3
mac_arch choices: both, arm64, x64

What this post does not claim

  • That a broken Intel app ever reached a user. The fix commit's own message says the cross-built app “shipped” without x64 binaries; the evidence we have is the verify probe's error, which fails the lane before anything is published, so we describe it as caught.
  • How many Luminair users are on Intel Macs. We did not look that up for this post.
  • How long we will keep building for Intel. For now it ships with every release.

Sources

For another release story where one change reached everyone at once, read The deploy that closed every Mac's socket for four hours.

Apple silicon or Intel

Luminair ships a signed, notarized app for both, with its engines built for your processor.

Download Luminair →