Skip to content

fix: build macOS x64/arm64 separately to prevent Intel architecture contamination#720

Merged
pedramamini merged 3 commits intomainfrom
fix/719-macos-intel-node-pty-architecture
Apr 4, 2026
Merged

fix: build macOS x64/arm64 separately to prevent Intel architecture contamination#720
pedramamini merged 3 commits intomainfrom
fix/719-macos-intel-node-pty-architecture

Conversation

@pedramamini
Copy link
Copy Markdown
Collaborator

@pedramamini pedramamini commented Apr 4, 2026

Summary

  • Splits the macOS build into separate x64 and arm64 packaging steps, each with its own native module rebuild (electron-rebuild --arch=<target>) and architecture verification before packaging
  • Removes explicit arch arrays from macOS targets in package.json, letting CLI flags (--x64 / --arm64) control what gets built — mirroring the Linux fix from ARM pty.node shipping with non ARM Linux releases... #116
  • Adds pre-packaging file checks for pty.node and better_sqlite3.node on macOS (previously only Linux had these safeguards)

Root cause: The macOS CI ran on an ARM64 runner and built both architectures in a single electron-builder invocation. With npmRebuild: false, native modules were only compiled for the host arch (arm64), so the x64 package shipped arm64 binaries — breaking terminal on Intel Macs.

Closes #719

Test plan

  • Trigger a release build and verify both macOS x64 and arm64 DMGs/ZIPs are produced
  • Verify the architecture verification steps pass in CI logs (x86_64 for x64, arm64 for arm64)
  • Install the x64 DMG on an Intel Mac and confirm terminal starts without "Failed to start terminal" error
  • Install the arm64 DMG on Apple Silicon and confirm terminal works
  • Verify file on pty.node inside each packaged app shows the correct architecture

Summary by CodeRabbit

  • Chores
    • macOS packaging now produces separate x64 and arm64 builds with per-architecture native rebuilds and verification.
    • Windows-only adjustment for non-Linux native-module rebuilds.
    • Linux packaging unified to run architecture-specific rebuild-and-verify steps before packaging.
  • Documentation
    • Normalized release notes formatting for consistent Markdown rendering.

…tamination

The macOS build ran on an ARM64 runner but built both architectures in
a single electron-builder invocation. Since npmRebuild was disabled,
native modules (node-pty, better-sqlite3) were only compiled for the
host arch (arm64), causing the x64 package to ship arm64 binaries.

This mirrors the Linux fix from #116: build each architecture
separately with explicit native module rebuild and verification
before packaging.

Closes #719
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 4, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ed73d919-0f8b-480f-811b-aa1ec4db80fb

📥 Commits

Reviewing files that changed from the base of the PR and between 1676385 and 646781c.

📒 Files selected for processing (5)
  • .github/scripts/rebuild-and-verify-native.sh
  • .github/scripts/verify-native-arch.sh
  • .github/workflows/release.yml
  • package.json
  • src/__tests__/main/stats/integration.test.ts

📝 Walkthrough

Walkthrough

Release workflow split macOS packaging into per-architecture steps, moved native rebuild/verification into shared scripts, simplified mac build targets in package.json, normalized release notes formatting, and updated a test to assert the new scripts are referenced.

Changes

Cohort / File(s) Summary
Workflow / CI
.github/workflows/release.yml
Split macOS packaging into separate "Package for macOS x64" and "Package for macOS arm64" steps; run per-arch rebuild/verify scripts before packaging; run Windows-only non-Linux native rebuild logic; removed inline Linux/mac arch verification in favor of shared scripts.
Shared CI scripts
.github/scripts/rebuild-and-verify-native.sh, .github/scripts/verify-native-arch.sh
Added `rebuild-and-verify-native.sh <x64
Packaging config
package.json
Simplified build.mac.target to plain targets ("dmg", "zip") instead of arch-specific target objects; updated lint-staged Prettier to --ignore-unknown.
Docs
docs/releases.md
Normalized Markdown formatting and removed stray CRLF/spacing inconsistencies across release notes.
Tests
src/__tests__/main/stats/integration.test.ts
Updated test to assert release.yml references the new shared scripts and moved the electron-rebuild content check into the rebuild-and-verify-native.sh script content read by the test.

Sequence Diagram(s)

sequenceDiagram
    participant Actions as GitHub Actions (release.yml)
    participant Script as rebuild-and-verify-native.sh
    participant Rebuild as electron-rebuild
    participant Verifier as verify-native-arch.sh
    participant Builder as electron-builder
    participant NodeMods as node_modules

    Actions->>Script: invoke ./.github/scripts/rebuild-and-verify-native.sh <arch>
    Script->>NodeMods: remove build/prebuilds for native modules
    Script->>Rebuild: run npx electron-rebuild --arch=<arch> --force
    Rebuild-->>NodeMods: produce rebuilt native binaries
    Script->>Verifier: call verify-native-arch.sh <arch> (or run file checks)
    Verifier->>NodeMods: inspect binary files with file command
    Verifier-->>Script: success/failure
    Script-->>Actions: exit status
    Actions->>Builder: run electron-builder --<arch> --config.extraMetadata.version=...
    Builder-->>Actions: packaged artifacts
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰
Binaries hopped to their promised isle,
x64 and arm64 — each with a smile,
I rebuild, I check, I guard the tree,
Packaged snug for you and me. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Out of Scope Changes check ❓ Inconclusive The docs/releases.md formatting normalization is out-of-scope; it addresses Markdown hygiene unrelated to the architecture contamination issue but is a minor maintenance improvement. Consider removing the docs/releases.md formatting changes from this PR and submitting them separately to keep the fix focused on the architecture issue.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: splitting macOS x64/arm64 builds separately to prevent architecture contamination, which directly addresses the root cause in issue #719.
Linked Issues check ✅ Passed The PR implements all coding requirements from #719: separate x64/arm64 builds with architecture-specific rebuilds [#719], native binary verification using file checks [#719], and removal of explicit arch arrays from package.json [#719].
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/719-macos-intel-node-pty-architecture

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 4, 2026

Greptile Summary

This PR fixes Intel Mac terminal failures caused by arm64 native binaries being packaged into the x64 DMG. The root cause was that the macOS CI runner (arm64) built both architectures in a single electron-builder invocation with npmRebuild: false, so native modules were only compiled for the host arch (arm64).

Key changes:

  • Splits the macOS packaging into two sequential steps — one for x64, one for arm64 — each with a clean, rebuild, architecture verification gate, and separate electron-builder invocation.
  • Removes hardcoded arch arrays from mac.target in package.json, deferring to --x64/--arm64 CLI flags (mirrors the existing Linux pattern).
  • Removes the Rebuild native modules (non-Linux) step that previously ran on macOS (now redundant since each packaging step performs its own targeted rebuild).
  • Adds pre-packaging file-based architecture checks for both pty.node and better_sqlite3.node on macOS, matching the existing Linux safeguards.

Issues found:

  • The electron-rebuild calls in both new macOS steps are missing the -w node-pty,better-sqlite3 scope filter (used in the postinstall script), meaning all native modules in node_modules will be rebuilt — including ones that may not support cross-compilation — which could cause unexpected build failures.
  • The macOS matrix entry's arch: universal label is now stale; the build no longer produces a universal binary.

Confidence Score: 4/5

Safe to merge — the fix correctly addresses the root cause; two minor issues (missing -w module filter and stale matrix label) are style-level and unlikely to break the build in practice.

The architecture separation pattern mirrors the already-working Linux approach and includes solid pre-packaging verification gates. The only actionable issues are that electron-rebuild rebuilds more modules than necessary (missing -w filter) and an outdated matrix arch label — neither blocks correctness of the produced artifacts.

.github/workflows/release.yml — specifically the electron-rebuild invocations at lines 354 and 414 which lack the -w node-pty,better-sqlite3 scope filter.

Important Files Changed

Filename Overview
.github/workflows/release.yml Core fix: replaces single combined macOS build step with separate x64 and arm64 steps, each with explicit native module clean, rebuild, architecture verification, and electron-builder invocation. The electron-rebuild calls are missing the -w node-pty,better-sqlite3 scope filter, which could rebuild all native modules unnecessarily. Matrix arch: universal is now stale/misleading.
package.json Removes explicit arch arrays from macOS targets in the electron-builder config, allowing CLI --x64/--arm64 flags to control the target architecture — consistent with the existing Linux approach.
docs/releases.md Documentation-only update: adds release notes for v0.8.0 and reformats existing v0.6.x changelog entries (whitespace/blank-line normalisation). No functional changes.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[macOS CI Runner\nmacos-latest / arm64] --> B[Checkout + npm ci]
    B --> C[Build Application\nnpm run build]
    C --> D[Import Apple Certificate]

    D --> E["Package for macOS x64"]
    E --> E1["Clean node-pty + better-sqlite3\nbuild dirs"]
    E1 --> E2["electron-rebuild --arch=x64 --force"]
    E2 --> E3{"Verify pty.node\ncontains x86_64?"}
    E3 -->|No| E4["❌ Exit 1"]
    E3 -->|Yes| E5{"Verify better_sqlite3.node\ncontains x86_64?"}
    E5 -->|No| E4
    E5 -->|Yes| E6["electron-builder --mac --x64\n→ DMG + ZIP artifacts"]

    D --> F["Package for macOS arm64"]
    F --> F1["Clean node-pty + better-sqlite3\nbuild dirs"]
    F1 --> F2["electron-rebuild --arch=arm64 --force"]
    F2 --> F3{"Verify pty.node\ncontains arm64?"}
    F3 -->|No| F4["❌ Exit 1"]
    F3 -->|Yes| F5{"Verify better_sqlite3.node\ncontains arm64?"}
    F5 -->|No| F4
    F5 -->|Yes| F6["electron-builder --mac --arm64\n→ DMG + ZIP artifacts"]

    E6 --> G[Upload Release Assets]
    F6 --> G
Loading

Comments Outside Diff (1)

  1. .github/workflows/release.yml, line 22 (link)

    P2 Stale arch: universal in macOS matrix entry

    The macOS matrix entry still shows arch: universal, but the PR no longer produces a universal binary — it now produces separate x64 and arm64 artifacts. While this value is currently only used for Linux cache-key templating (so it doesn't break anything), it's now misleading documentation.

    Consider updating it to reflect the new build strategy, e.g. arch: x64+arm64 or removing it entirely for the macOS entry, to keep the matrix self-documenting.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Reviews (1): Last reviewed commit: "style: fix prettier formatting in docs/r..." | Re-trigger Greptile


# Rebuild native modules explicitly for x64
echo "Rebuilding native modules for x64..."
npx electron-rebuild --arch=x64 --force
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing -w module filter in electron-rebuild

The postinstall script intentionally limits the rebuild to only the two native modules that need architecture-specific binaries (-w node-pty,better-sqlite3):

electron-rebuild -f -w node-pty,better-sqlite3

The new macOS x64 step calls electron-rebuild without a -w filter, which will attempt to rebuild all native modules found in node_modules. If any native module (e.g., fsevents or other platform-specific addons) doesn't cleanly support cross-compilation from arm64 → x64, the step will fail.

The same issue exists in the arm64 step at line 414.

Suggested change
npx electron-rebuild --arch=x64 --force
npx electron-rebuild --arch=x64 --force -w node-pty,better-sqlite3


# Rebuild native modules explicitly for arm64
echo "Rebuilding native modules for arm64..."
npx electron-rebuild --arch=arm64 --force
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing -w module filter in electron-rebuild (arm64 step)

Same issue as the x64 step — no -w filter means all native modules will be rebuilt. For consistency with postinstall and to reduce risk of unrelated native modules failing cross-compilation, scope the rebuild to just the two required modules.

Suggested change
npx electron-rebuild --arch=arm64 --force
npx electron-rebuild --arch=arm64 --force -w node-pty,better-sqlite3

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
.github/workflows/release.yml (1)

330-451: Consider: Both macOS architectures built sequentially on same runner.

The sequential x64→arm64 build on a single runner (macos-latest, which is arm64) works correctly because each step cleans and rebuilds native modules. However, this approach has trade-offs:

Pros:

  • Uses only one runner/job
  • Certificate import happens once

Trade-off: If the x64 step fails after electron-builder partially completes, the arm64 step could succeed, but the release artifacts might include partial x64 files. The if-no-files-found: error on artifact upload (line 692) should catch missing expected files.

This is acceptable for the current design. Just noting it for visibility.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 330 - 451, Add an explicit
post-package verification after the "Package for macOS x64" step (the block that
runs npx electron-builder --mac --x64 ...) to confirm the expected x64 artifact
(e.g., .dmg/.zip/.app in the dist output) exists and fail early if missing; keep
the existing cleanup/rebuild logic but add a simple file-existence check and
exit 1 with a clear message if the artifact is not found so the subsequent arm64
build doesn't proceed with partial x64 outputs (this complements the existing
if-no-files-found: error behavior used during artifact upload).
docs/releases.md (1)

202-202: Optional: Correct "Github" capitalization.

The official brand name is "GitHub" with a capital "H". As per static analysis hints, line 202 uses "Github Worktree" instead of "GitHub Worktree".

📝 Suggested fix
-🌳 Github Worktree support was added. Any agent bound to a Git repository has the option to enable worktrees, each of which show up as a sub-agent with their own write-lock and Auto Run capability. Now you can truly develop in parallel on the same project and issue PRs when you're ready, all from within Maestro. Huge improvement, major thanks to `@petersilberman`.
+🌳 GitHub Worktree support was added. Any agent bound to a Git repository has the option to enable worktrees, each of which show up as a sub-agent with their own write-lock and Auto Run capability. Now you can truly develop in parallel on the same project and issue PRs when you're ready, all from within Maestro. Huge improvement, major thanks to `@petersilberman`.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/releases.md` at line 202, Replace the incorrect "Github Worktree"
capitalization with the official "GitHub Worktree" in the release note text (the
string containing "Github Worktree was added. Any agent bound to a Git
repository...") so the brand name is correctly rendered as "GitHub" throughout
that sentence; ensure any other occurrences of "Github" in the same paragraph
are updated to "GitHub".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.github/workflows/release.yml:
- Around line 330-451: Add an explicit post-package verification after the
"Package for macOS x64" step (the block that runs npx electron-builder --mac
--x64 ...) to confirm the expected x64 artifact (e.g., .dmg/.zip/.app in the
dist output) exists and fail early if missing; keep the existing cleanup/rebuild
logic but add a simple file-existence check and exit 1 with a clear message if
the artifact is not found so the subsequent arm64 build doesn't proceed with
partial x64 outputs (this complements the existing if-no-files-found: error
behavior used during artifact upload).

In `@docs/releases.md`:
- Line 202: Replace the incorrect "Github Worktree" capitalization with the
official "GitHub Worktree" in the release note text (the string containing
"Github Worktree was added. Any agent bound to a Git repository...") so the
brand name is correctly rendered as "GitHub" throughout that sentence; ensure
any other occurrences of "Github" in the same paragraph are updated to "GitHub".

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 79096e01-b485-4668-8144-ef407dd6f8b9

📥 Commits

Reviewing files that changed from the base of the PR and between 626cd6f and 1676385.

📒 Files selected for processing (3)
  • .github/workflows/release.yml
  • docs/releases.md
  • package.json

@pedramamini
Copy link
Copy Markdown
Collaborator Author

Thanks for the contribution, @pedramamini! 🙌

Reviewed the changes — the approach is solid. Splitting the macOS build into separate x64/arm64 steps with per-architecture rebuild + verification gates correctly addresses the root cause (arm64 runner contaminating x64 packages). The pattern mirrors the existing Linux fix from #116, which is great for consistency.

The package.json simplification and docs formatting cleanup are both clean.

LGTM — approving.

Deduplicates ~260 lines of copy-pasted clean/rebuild/verify logic across
macOS x64, macOS arm64, Linux x64, and Linux ARM64 packaging steps into
two reusable scripts:

- rebuild-and-verify-native.sh: clean + rebuild + verify (used before packaging)
- verify-native-arch.sh: verify only (used after initial postinstall)

Also drops the unrelated docs/releases.md whitespace-only changes that
inflated the original PR diff, and adds --ignore-unknown to prettier in
lint-staged so shell scripts and other non-parseable files don't fail
the pre-commit hook.
@pedramamini pedramamini merged commit 4167d15 into main Apr 4, 2026
2 of 3 checks passed
@pedramamini pedramamini deleted the fix/719-macos-intel-node-pty-architecture branch April 4, 2026 23:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Terminal fails on Intel Macs — "Failed to start terminal / Check system PTY availability"

1 participant