Repository navigation
fix(host): isolate any per-plugin bootstrap failure — one failed plugin must not take down the plugin system - #675
Merged
Conversation
…fig errors no longer take down the whole plugin system (#672) - secrets resolution and auth base-variable derivation moved inside the guarded per-plugin load; failures and thrown defects are normalized into PluginBootstrapError (pluginFailureCause) and recorded per plugin - failure entries now feed every kind (auth/api included) a failedPlugins client entry so dependents fail loudly instead of the loop dying - interrupts-only causes are re-raised; a caught defect names the plugin and its failing stage - regression coverage: pluginFailureCause unit tests + an initializePlugins integration suite booting a failing plugin beside a healthy one
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #672.
Problem
initializePluginsisolated the common failure (typedPluginBootstrapErrorfromloadRemoteWithRetry) per plugin, but three other error classes escaped the loop'scatchTagand hit the outerEffect.catch→unavailableResult(...)— discarding all plugins, including ones that had already loaded, and leaving the host without auth/api:ConfigErrorfrom secrets resolution (Config.Redactedreads) — a typed failure the catchTag never matchedbuildAuthBaseVariables's production "No reachable origin" throw (and any other throw in the per-plugin path) — dies are not catchable byEffect.catchat all, so the fiber died and the whole plugin layer failed, preventing the host from startingregisterAppSharedDeps(all-or-nothing by construction, kept global intentionally)Fix
loadPluginEntryEffect, under the guarded pipeline.Effect.catchTagis nowEffect.catchCauseIf: any failing cause (typedPluginBootstrapError, foreignConfigError, thrown defect) is normalized via the new exportedpluginFailureCauseand recorded throughrecordPluginFailure— already-loaded plugins are kept.hasInterruptsOnlypredicate) rather than swallowed.failedPluginsClientEntry, so dependents fail loudly with a clear message instead of the loop aborting.createPluginRuntime/registerAppSharedDeps) keep the previous degraded-all behavior — nothing can load without the runtime.Behavioral check
With this change, booting a config whose auth plugin dies mid-bootstrap (production origin misconfig — previously a fatal boot failure) now logs the failure, records it in
/health'sstatus.failures, and continues to load every other configured plugin. Host consumers were already null-safe (createSessionMiddlewareserves without sessions,/api/*503s without the API, SSR compose is plugin-independent), so the host still runs and exposes the UI.Tests
host/tests/unit/plugins-failure-cause.test.ts— normalization: typed errors pass through unchanged; defects and foreign failures synthesize a stage-initPluginBootstrapErrornaming the plugin and URL.host/tests/integration/plugin-failure-isolation.test.ts— drivesinitializePluginswith a stubbed plugin runtime (real DAG + real classification/retry machinery): a plugin whose bundle fails to load doesn't prevent a healthy sibling from loading; a plugin whose bootstrap dies fatally (production origin misconfig) is recorded (failed: auth,No reachable origin) whilevotesstill loads.tsc --noEmitclean, full host suite green (247 unit+integration tests),biome checkandoxlint --type-awareclean on touched files.