Skip to content

Fix macOS Whisper runtime packaging#4

Merged
duetCJackson merged 3 commits into
mainfrom
codex/fix-macos-whisper-runtime-artifact
May 13, 2026
Merged

Fix macOS Whisper runtime packaging#4
duetCJackson merged 3 commits into
mainfrom
codex/fix-macos-whisper-runtime-artifact

Conversation

@duetCJackson

Copy link
Copy Markdown
Collaborator

Summary

This fixes the macOS local speech engine install/runtime failures seen in AD-77 and the AD-65 comment logs by shipping a self-contained arm64 Whisper runtime in the app bundle instead of patching Homebrew binaries on customer machines.

What changed

  • Added a repeatable prepare:macos-whisper-runtime build step that stages the arm64 whisper-cpp, libwhisper, ggml, backend plugins, and libomp runtime files.
  • Rewrites runtime load commands and GGML's compiled backend search path so the packaged runtime loads from the app/local models directory, not /opt/homebrew.
  • Packages the runtime through extraResources at Contents/Resources/macos-whisper-runtime/arm64.
  • Updates mac build/release CI to prepare the runtime before packaging and installs required Homebrew source packages on mac runners.
  • Adds runtime-link failure classification/recovery so dyld linker failures reinstall only the runtime and do not redownload the speech model.
  • Removes the old customer-machine install_name_tool / codesign patching path.
  • Adds packaged-app Playwright coverage that verifies a clean user-data install from the compiled app bundle.

Root cause

The v0.1.23-era install path still depended on customer-machine binary rewriting and/or Homebrew-style runtime layout. On machines without Xcode command line tools, install_name_tool failed. On machines with incomplete rewritten runtime links, whisper-cpp failed with dyld @rpath/libwhisper.1.dylib load errors. Even after dylib load-command fixes, Homebrew's GGML build embedded a backend plugin search path that could still load /opt/homebrew/Cellar/ggml/... plugins unless patched.

Validation

  • npm run prepare:macos-whisper-runtime
  • npm run build
  • AUTODOC_DISABLE_ASAR_INTEGRITY=1 CSC_IDENTITY_AUTO_DISCOVERY=false npx electron-builder --mac dir --publish never --config.mac.identity=null --config.mac.notarize=false
  • AUTODOC_RUN_PACKAGED_MACOS_WHISPER_RUNTIME_TEST=1 AUTODOC_PACKAGED_APP_PATH=/Volumes/DuetDrive/Repos/AutoDocLocal/dist/mac-arm64/AutoDoc.app npm run test:e2e -- e2e/macos-packaged-whisper-runtime.spec.ts
  • npm run test:main:run -- src/main/services/__tests__/whisper-onboarding-install.test.ts src/main/services/__tests__/whisper-manager.test.ts
  • npm run typecheck:node
  • git diff --check

Local note: the unsigned local mac dir build needed AUTODOC_DISABLE_ASAR_INTEGRITY=1, matching the repo's existing mac smoke-test note for this volume.

@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro (Legacy)

Run ID: 5078f5ff-47db-43cf-b5c1-561da123076b

📥 Commits

Reviewing files that changed from the base of the PR and between 659e992 and 912dd04.

📒 Files selected for processing (7)
  • e2e/README.md
  • e2e/helpers/electron-app.ts
  • scripts/prepare-macos-whisper-runtime.js
  • src/main/index.ts
  • src/main/services/__tests__/whisper-manager.test.ts
  • src/main/services/__tests__/whisper-onboarding-install.test.ts
  • src/main/services/whisper-manager.ts

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • macOS app now bundles a Whisper runtime and can install it from the packaged installer for improved offline usability.
  • Tests

    • Added end-to-end and unit tests validating packaged macOS Whisper runtime installation and runtime-link recovery.
  • Chores

    • Updated macOS build and CI configuration to prepare and include the bundled Whisper runtime in packaged apps.

Walkthrough

Prepares an arm64 macOS Whisper runtime at build time, bundles it into the app, adds installation and recovery paths to WhisperManager, and adds E2E and unit tests plus post-pack signing and CI preparation steps.

Changes

Bundled macOS Whisper Runtime

Layer / File(s) Summary
Build and packaging pipeline
.github/workflows/build.yml, package.json, electron-builder.yml
CI installs required Homebrew packages; prepare:macos-whisper-runtime runs before mac build/release; electron-builder includes resources/macos-whisper-runtime under macos-whisper-runtime.
Runtime preparation script
scripts/prepare-macos-whisper-runtime.js
CLI copies whisper/ggml/libomp artifacts, rewrites Mach-O install names and backend search paths, ad-hoc codesigns and verifies files, removes AppleDouble files, runs a whisper-cpp --help smoke check, and logs bundle metadata.
Post-pack signing and cleanup
scripts/after-pack-mac-privacy.cjs
Removes ._* AppleDouble files from bundled runtime and signs bundled Whisper runtime binaries using a shared signing helper with label-driven logging.
Runtime installation and asset configuration
src/main/services/whisper-manager.ts
Adds bundled-runtime-first installation, installBundledMacWhisperRuntimeOnly(), tarball SHA verification and extraction fallback, recursive copy into managed models directory with chmod, and removal of prior managed artifacts.
Startup validation and runtime recovery
src/main/services/whisper-manager.ts
Adds runtime-link-failure classification for macOS dyld link errors; ensureReady() will recover by reinstalling/remediating the runtime and re-probing; adds recoverFromRuntimeValidationFailure().
Startup policy bypass and E2E IPC handler
src/main/index.ts
Reads AUTODOC_SKIP_INSTALL_POLICY to optionally skip installed-app policy enforcement, adjusts close behavior during real-setup tests, and registers e2e:install-bundled-mac-whisper-runtime IPC handler.
E2E testing infrastructure and spec
e2e/README.md, e2e/helpers/electron-app.ts, e2e/macos-packaged-whisper-runtime.spec.ts
Adds recommended command for packaged runtime test; helper launchPackagedRealSetupApp to start the packaged app from a bundle; Playwright spec validates packaged runtime files, IPC-driven install, binary portability, and cleanup.
Unit test updates
src/main/services/__tests__/whisper-manager.test.ts, src/main/services/__tests__/whisper-onboarding-install.test.ts
Adds a unit test for macOS dyld probe classification; removes legacy macOS signing/rewriting tests; onboarding tests update imports/mocks and add packaged-runtime-focused test cases.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title "Fix macOS Whisper runtime packaging" directly and clearly summarizes the main change—replacing customer-machine binary patching with a bundled arm64 Whisper runtime in the app.
Description check ✅ Passed The description is comprehensive and directly related to the changeset. It explains the root cause, what changed, and includes validation steps matching the file modifications across the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • LINEAR integration encountered authorization issues. Please disconnect and reconnect the integration in the CodeRabbit UI.

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

@duetCJackson duetCJackson changed the title [codex] Fix macOS Whisper runtime packaging Fix macOS Whisper runtime packaging May 13, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@e2e/helpers/electron-app.ts`:
- Around line 160-166: The env block that spreads process.env into the Electron
launch (the object containing NODE_ENV, AUTODOC_TEST_REAL_SETUP,
AUTODOC_TEST_USER_DATA_DIR/userDataDir, AUTODOC_SKIP_INSTALL_POLICY and
...(extraEnv ?? {})) can inherit AUTODOC_E2E from the parent, preventing a true
real-setup launch; explicitly set AUTODOC_E2E to '0' in that env object (after
the spread) so it overrides any upstream AUTODOC_E2E, ensuring the real packaged
path runs when AUTODOC_TEST_REAL_SETUP is used.

In `@e2e/README.md`:
- Line 13: The README command can break if the .app path contains spaces; update
the documented command so the AUTODOC_PACKAGED_APP_PATH env assignment value is
quoted (e.g., AUTODOC_PACKAGED_APP_PATH="/abs/path/to/AutoDoc.app") in the
example line that shows the test invocation (the line containing
AUTODOC_RUN_PACKAGED_MACOS_WHISPER_RUNTIME_TEST and AUTODOC_PACKAGED_APP_PATH
used with npm run test:e2e).

In `@scripts/prepare-macos-whisper-runtime.js`:
- Around line 228-241: The prep script currently builds runtimeFiles by
spreading the result of copyMatchingFiles(...) but doesn't check for an empty
result; update the logic in prepare-macos-whisper-runtime.js so you first await
copyMatchingFiles(path.join(ggmlPrefix, 'libexec'), /^libggml.*\.so$/i) into a
variable (e.g., ggmlPlugins), and if ggmlPlugins.length === 0 throw an Error or
call process.exit(1) with a clear message that no GGML backend plugins were
staged; then include ...ggmlPlugins when constructing runtimeFiles so the script
fails early instead of silently shipping without backend plugins (REQUIRED_FILES
and the whisper-cpp --help smoke test remain unchanged).

In `@src/main/index.ts`:
- Line 94: The skipInstalledApplicationPolicy flag currently trusts
AUTODOC_SKIP_INSTALL_POLICY alone; tighten the guard so the bypass is only
allowed in non-production contexts by combining the env check with an explicit
non-production condition (e.g., process.env.NODE_ENV !== 'production' or an
isDev/isTest helper) when initializing skipInstalledApplicationPolicy and when
it is consumed elsewhere (reference the skipInstalledApplicationPolicy variable
and the AUTODOC_SKIP_INSTALL_POLICY env var and the other usage point where the
flag is read). If you need an override for prod, require a distinct explicit
opt-in env (e.g., AUTODOC_ALLOW_SKIP_IN_PROD) and validate that is set before
allowing the bypass.

In `@src/main/services/__tests__/whisper-manager.test.ts`:
- Around line 135-151: The runtime-link-failure classification in
classifyWhisperProbeFailure is not gated by OS; update the function so that the
logic that detects macOS dyld messages (e.g., matching "dyld" / "Library not
loaded" / libwhisper.*.dylib in the probe stderr) only returns
'runtime-link-failure' when process.platform === 'darwin', otherwise return the
existing fallback (e.g., 'failed'); modify the detection branch inside
classifyWhisperProbeFailure to check process.platform before emitting
'runtime-link-failure'.

In `@src/main/services/whisper-manager.ts`:
- Around line 57-62: Update the macOS runtime validation to require the full set
of packaged runtime files instead of only the four cores: add 'libomp.dylib' to
MAC_WHISPER_RUNTIME_EXPECTED_FILES and also require any ggml backend plugins by
matching patterns like /^libggml(-.*)?\.dylib$/ (or explicitly include plugin
names if known); ensure the same updated constant/patterns are used by both
bundled-runtime discovery and extracted-runtime root detection in
whisper-manager.ts so partially staged runtimes with missing libomp or ggml
backend plugins are not treated as complete.
- Around line 1396-1404: The macRuntimeLinkFailure detection (variable
macRuntimeLinkFailure) currently only matches `@rpath` dyld errors against
combinedOutput and err.message; update the regex checks used where
combinedOutput and `${err.message}\n${combinedOutput}` are tested to also match
dyld messages that contain `@executable_path` or `@loader_path` (e.g. add
/@executable_path\/lib(?:whisper|ggml|omp)/i and
/@loader_path\/lib(?:whisper|ggml|omp)/i alternatives alongside the existing
`@rpath` pattern) so that the runtime-link-failure branch triggers for packaged
rewrites as well.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro (Legacy)

Run ID: e5b6060e-1438-4d05-b52e-60c457d9b053

📥 Commits

Reviewing files that changed from the base of the PR and between f79264b and 9d0b745.

⛔ Files ignored due to path filters (9)
  • resources/macos-whisper-runtime/arm64/libggml-base.0.dylib is excluded by !**/*.dylib
  • resources/macos-whisper-runtime/arm64/libggml-blas.so is excluded by !**/*.so
  • resources/macos-whisper-runtime/arm64/libggml-cpu-apple_m1.so is excluded by !**/*.so
  • resources/macos-whisper-runtime/arm64/libggml-cpu-apple_m2_m3.so is excluded by !**/*.so
  • resources/macos-whisper-runtime/arm64/libggml-cpu-apple_m4.so is excluded by !**/*.so
  • resources/macos-whisper-runtime/arm64/libggml-metal.so is excluded by !**/*.so
  • resources/macos-whisper-runtime/arm64/libggml.0.dylib is excluded by !**/*.dylib
  • resources/macos-whisper-runtime/arm64/libomp.dylib is excluded by !**/*.dylib
  • resources/macos-whisper-runtime/arm64/libwhisper.1.dylib is excluded by !**/*.dylib
📒 Files selected for processing (13)
  • .github/workflows/build.yml
  • e2e/README.md
  • e2e/helpers/electron-app.ts
  • e2e/macos-packaged-whisper-runtime.spec.ts
  • electron-builder.yml
  • package.json
  • resources/macos-whisper-runtime/arm64/whisper-cpp
  • scripts/after-pack-mac-privacy.cjs
  • scripts/prepare-macos-whisper-runtime.js
  • src/main/index.ts
  • src/main/services/__tests__/whisper-manager.test.ts
  • src/main/services/__tests__/whisper-onboarding-install.test.ts
  • src/main/services/whisper-manager.ts

Comment thread e2e/helpers/electron-app.ts
Comment thread e2e/README.md Outdated
Comment thread scripts/prepare-macos-whisper-runtime.js
Comment thread src/main/index.ts Outdated
Comment thread src/main/services/__tests__/whisper-manager.test.ts
Comment thread src/main/services/whisper-manager.ts
Comment thread src/main/services/whisper-manager.ts
@duetCJackson duetCJackson marked this pull request as ready for review May 13, 2026 19:54
@duetCJackson duetCJackson merged commit 7893c3f into main May 13, 2026
9 checks passed
@duetCJackson duetCJackson deleted the codex/fix-macos-whisper-runtime-artifact branch June 26, 2026 20:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant