Skip to content

fix(plugins): surface the real reason a plugin bundle fails to load (#1915)#1927

Merged
datlechin merged 1 commit into
mainfrom
fix/plugin-bundle-load-diagnostics
Jul 21, 2026
Merged

fix(plugins): surface the real reason a plugin bundle fails to load (#1915)#1927
datlechin merged 1 commit into
mainfrom
fix/plugin-bundle-load-diagnostics

Conversation

@datlechin

Copy link
Copy Markdown
Member

Fixes #1915.

Root cause

Bundle.load() returns a bare Bool and discards the NSError that dyld/NSBundle produced. Two call sites relied on it, so every distinct load failure (wrong architecture, missing Swift-runtime dylib, incompatible runtime, missing symbol from an ABI break, damaged or unsigned executable) collapsed to one unactionable string: "Invalid plugin bundle: Bundle failed to load executable". The user in #1915 hit this installing Oracle 1.2.17, which weak-links libswiftSynchronization.dylib and fails as a missing-dependency load error that today looks identical to an arch mismatch.

A second, quieter defect sat next to it: the on-demand activation path only logged the failure and returned, recording no rejected-plugin entry, so a lazily-loaded driver that failed to load just never appeared with no diagnostic anywhere.

What changed

  • New PluginBundleLoader loads a bundle with Bundle.loadAndReturnError() (the documented API that keeps the NSError), logs the full dyld detail via OSLog for support, and translates NSError.code into an actionable, localized reason. It mirrors the existing PluginCodeSignatureVerifier.describeOSStatus pattern. The classification separates:
    • wrong architecture (NSExecutableArchitectureMismatchError)
    • incompatible runtime (NSExecutableRuntimeMismatchError)
    • missing or incompatible dependency (NSExecutableLoadError, the Oracle case)
    • missing symbol / incompatible with this app version (NSExecutableLinkError, the ABI-break case behind Lỗi sử dụng Mongodb ở bản 0.58 #1917)
    • damaged or improperly signed executable (NSExecutableNotLoadableError)
    • missing executable file (NSFileNoSuchFileError)
  • PluginManager routes both bundle.load() sites through PluginBundleLoader.load. The install path throws the specific reason up to the existing alerts; the lazy-activation path now records a RejectedPlugin instead of silently returning, so the failure surfaces in the rejected-plugin banner and notification.
  • PluginError is unchanged: .invalidBundle now carries the real reason, so every existing alert improves without new UI.

The full dyld text (NSDebugDescriptionErrorKey) goes to OSLog only, per Apple's guidance that it is developer-facing; the user sees the concise classification.

Scope

No PluginKit ABI change, so no version bump and no plugin re-release. The issue's second point (Oracle 1.2.17 registry minAppVersion vs. its build toolchain) is a release-metadata fix in the TableProApp/plugins repo, not app code. This change is what makes such mismatches diagnosable.

Tests

  • PluginBundleLoaderTests covers the code-to-reason translation for all six error codes plus the OS-provided fallback. Deterministic, always runs.
  • PluginLazyActivationVerificationTests gains a test that builds a bundle with a non-Mach-O executable, activates it, and asserts it is now rejected with a non-generic reason. This guards the silent-swallow fix.

Notes for review

swiftlint lint --strict is clean on the changed files. The import reorder in PluginLazyActivationVerificationTests fixes a pre-existing sorted_imports warning that only shows under --strict.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7ca27b73c7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

try PluginBundleLoader.load(bundle)
} catch {
Self.logger.error("Failed to load lazy bundle '\(bundleId)' at \(url.lastPathComponent): \(error.localizedDescription)")
recordLazyActivationRejection(url: url, bundleId: bundleId, entry: entry, error: error)

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 Badge Emit the rejection event after a lazy load failure

When a lazy bundle fails after initial startup, this only appends to rejectedPlugins; no AppEvents.shared.pluginsRejected event is sent. PluginNotificationService subscribes exclusively to that event (set up in AppDelegate), while the only sender is the initial reconciliation path that has already run, so the newly added rejected-plugin notification is never delivered for this on-demand failure despite the intended reporting behavior.

Useful? React with 👍 / 👎.

@datlechin
datlechin merged commit 0e72a55 into main Jul 21, 2026
3 checks passed
@datlechin
datlechin deleted the fix/plugin-bundle-load-diagnostics branch July 21, 2026 06:53
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.

Oracle plugin fails to load with generic invalid bundle error

1 participant