diff --git a/packages/core/src/plugin.ts b/packages/core/src/plugin.ts index ca3175c96a9..d6b11356ed1 100644 --- a/packages/core/src/plugin.ts +++ b/packages/core/src/plugin.ts @@ -143,8 +143,14 @@ const layer = Layer.effect( ([id, slot]) => Effect.gen(function* () { active.delete(id) - if (slot.activation && !slot.activation.failure) - yield* Scope.close(slot.activation.scope, Exit.void) + if (!slot.activation || slot.activation.failure) return + // Later plugins, including the config agents and commands, are already closed. A failing + // finalizer must not abort the swap, or they stay unregistered until the next reload. + yield* Scope.close(slot.activation.scope, Exit.void).pipe( + Effect.catchCause((cause) => + Effect.logError("failed to clean up plugin", { "plugin.id": id, cause }), + ), + ) }), { discard: true }, ) diff --git a/packages/plugin/src/promise/adapter.ts b/packages/plugin/src/promise/adapter.ts index ba81e6ee224..ec91156ba46 100644 --- a/packages/plugin/src/promise/adapter.ts +++ b/packages/plugin/src/promise/adapter.ts @@ -672,7 +672,8 @@ export function fromPromise(plugin: Plugin) { yield* Effect.acquireRelease( Effect.promise(() => Promise.resolve(plugin.setup(context2))), - (cleanup) => (cleanup ? Effect.promise(() => Promise.resolve(cleanup())) : Effect.void), + // JavaScript plugins may return anything, such as a v1-style hooks object; only a function is a cleanup. + (cleanup) => (typeof cleanup === "function" ? Effect.promise(() => Promise.resolve(cleanup())) : Effect.void), ) }), })