Blocked-by: #11825
Found while implementing #11955 (making successThreshold bind from every status that
records a failure). Outside that card's surface — it is fenced to the success branch's
status gate — so this is filed rather than fixed.
What was measured
packages/core/src/health-monitor.ts at b797b30566, in attemptRestart:
try{// Call destroy and init to restartif(plugin.destroy){awaitplugin.destroy();}// Note: Full restart would require kernel context// This is a simplified version - actual implementation would need kernel integrationthis.logger.info('Plugin restarted',{plugin: pluginName});// Reset counters on successful restartthis.failureCounters.set(pluginName,0);this.successCounters.set(pluginName,0);this.healthStatus.set(pluginName,'recovering');}catch(error){…}The comment above the call says "Call destroy and init to restart". Only destroy() is
called. init() is never called, and nothing else in the file calls it — init appears in
health-monitor.ts exclusively inside that comment. So the sequence a plugin actually gets
is: destroy, then a log line reading Plugin restarted, then status recovering, then
periodic health checks continue to run against the destroyed instance.
Declared contract
packages/spec/src/kernel/plugin-lifecycle-advanced.zod.ts:
autoRestart: z.boolean().default(false).describe('Automatically restart plugin on health check failure'),content/docs/references/kernel/plugin-lifecycle-advanced.mdx:114 repeats it verbatim.
Neither says the restart is destroy-only, and the operator-visible signals all say the
opposite: the log line asserts Plugin restarted, restartAttempts is incremented as
though a restart occurred, and maxRestartAttempts / restartBackoff schedule further
"restarts" of a plugin that was never brought back up.
Why the severity moved
Before #11955, a restarted plugin sat at recovering and reached healthy on its next
successful check. After #11955 it reaches healthy after successThreshold consecutive
successful checks. Either way the terminal report for a destroyed, never-re-initialised
plugin is healthy — but the second shape is more convincing, because the plugin now has
to pass a declared number of consecutive checks to get there. The default health check when
no checkMethod resolves is { name: 'plugin-loaded', status: 'passed' }, which a
destroyed plugin passes indefinitely.
What is NOT claimed
Whether the right resolution is to implement a real restart, to stop claiming one, or to
retire the surface is not determined here, and it is downstream of #11825 — which records
that nothing reads .health and that the kernel never constructs PluginHealthMonitor at
all. The kernel context this code says it needs is exactly what #11825 is about, so this
should be triaged after that card settles: if the surface is retired, this is moot; if it
is wired up, the missing init() becomes implementable for the first time.
Back-links: #11955 · #11825 · PR #12031.
Generated by Claude Code
Blocked-by: #11825
Found while implementing #11955 (making
successThresholdbind from every status thatrecords a failure). Outside that card's surface — it is fenced to the success branch's
status gate — so this is filed rather than fixed.
What was measured
packages/core/src/health-monitor.tsatb797b30566, inattemptRestart:The comment above the call says "Call destroy and init to restart". Only
destroy()iscalled.
init()is never called, and nothing else in the file calls it —initappears inhealth-monitor.tsexclusively inside that comment. So the sequence a plugin actually getsis: destroy, then a log line reading
Plugin restarted, then statusrecovering, thenperiodic health checks continue to run against the destroyed instance.
Declared contract
packages/spec/src/kernel/plugin-lifecycle-advanced.zod.ts:content/docs/references/kernel/plugin-lifecycle-advanced.mdx:114repeats it verbatim.Neither says the restart is destroy-only, and the operator-visible signals all say the
opposite: the log line asserts
Plugin restarted,restartAttemptsis incremented asthough a restart occurred, and
maxRestartAttempts/restartBackoffschedule further"restarts" of a plugin that was never brought back up.
Why the severity moved
Before #11955, a restarted plugin sat at
recoveringand reachedhealthyon its nextsuccessful check. After #11955 it reaches
healthyaftersuccessThresholdconsecutivesuccessful checks. Either way the terminal report for a destroyed, never-re-initialised
plugin is
healthy— but the second shape is more convincing, because the plugin now hasto pass a declared number of consecutive checks to get there. The default health check when
no
checkMethodresolves is{ name: 'plugin-loaded', status: 'passed' }, which adestroyed plugin passes indefinitely.
What is NOT claimed
Whether the right resolution is to implement a real restart, to stop claiming one, or to
retire the surface is not determined here, and it is downstream of #11825 — which records
that nothing reads
.healthand that the kernel never constructsPluginHealthMonitoratall. The kernel context this code says it needs is exactly what #11825 is about, so this
should be triaged after that card settles: if the surface is retired, this is moot; if it
is wired up, the missing
init()becomes implementable for the first time.Back-links: #11955 · #11825 · PR #12031.
Generated by Claude Code