Skip to content

Five more Plugin implementations release their teardown from a hook the kernel never calls — the #10371 enumeration is short by five #10772

Description

@claude

Found while deriving the population for the #10619 teardown-shape gate. Filed rather than fixed — the repairs are the #10371 lane's work, and folding them into the gate's PR would have made it unreviewable against its own card.

The gap

#10371 enumerates six Plugin implementations whose teardown is spelled stop() with no destroy(). Re-derived mechanically over packages/** at main = 78ac958 — every class with an implements Plugin clause, every teardown-shaped method or arrow property it declares — the class is eleven, not six.

The five #10371 does not name split into two shapes, and neither is exotic:

Spelled as an arrow PROPERTY, not a method (a method-only reading of the class misses them):

fileclassaliaswhat it releases
packages/metadata/src/plugin.tsMetadataPluginstop = async (ctx) => …closes the artifact watcher, manager.dispose(), closes the repository
packages/runtime/src/app-plugin.tsAppPluginstop = async (ctx) => …emits app:unregistered to the control plane
packages/runtime/src/external-validation-plugin.tsExternalValidationPluginstop = (): void => …clearInterval over every armed drift-check timer

Spelled dispose(), not stop() — the seventh-spelling case #10619 predicts, already present:

fileclassaliaswhat it releases
packages/plugins/plugin-email/src/email-plugin.tsEmailServicePluginasync dispose()two metadata subscriptions, the live SMTP transport, an engine binding
packages/plugins/plugin-webhooks/src/webhook-outbox-plugin.tsWebhookOutboxPluginasync dispose()stops the auto-enqueuer, unbinds two engine hooks

Why these are the same defect, not near-misses

ObjectKernel.performShutdown() and LiteKernel.destroy() call plugin.destroy() and nothing else. Verified on the same revision: nothing in the tree calls stop(), dispose(), close() or shutdown() on a plugin. EmailServicePlugin.dispose() has exactly one caller in the whole repo — a test (email-plugin.template-runtime-write.test.ts); WebhookOutboxPlugin.dispose() has none at all.

ExternalValidationPlugin is the one that raises the stakes above "listed for whoever sweeps": it is the only instance besides plugin-reports that owns setInterval timers, and its stop() is reached only from scheduleDriftChecks() re-arming itself. On kernel shutdown those intervals are never cleared — the #9371 mechanism verbatim.

Shape of the repair

The same one PR #10375 applied: move the body into destroy() and keep the old name as a delegating alias, because it is public API of an exported class.

Status in tooling

All eleven are baselined in the #10619 gate's KNOWN_TEARDOWN_UNREACHED list, which is shrink-only and closed to new members. Deleting an entry as each plugin is repaired is the intended burn-down; a stale entry fails the gate, so the ledger cannot drift out of step with the repairs.


Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions