Found while implementing #11823 (documenting the third checks[].name in
lifecycle.mdx). Out of that card's scope — it is a docs-only card fenced to the
checks[].name enumeration — so this is filed rather than fixed.
What was measured
packages/core/src/health-monitor.ts at a1c804bc9. performHealthCheck reaches its
failure handling by two disjoint routes, and only one of them can restart the plugin.
Returned failure (:116-119 → :146-164) — the check returned false or
{ status: 'unhealthy' }:
this.failureCounters.set(pluginName,(this.failureCounters.get(pluginName)||0)+1);this.successCounters.set(pluginName,0);constfailureCount=this.failureCounters.get(pluginName)||0;if(failureCount>=config.failureThreshold){this.healthStatus.set(pluginName,'unhealthy');// ...if(config.autoRestart){awaitthis.attemptRestart(pluginName,plugin,config);// :159-161}}else{this.healthStatus.set(pluginName,'degraded');}Thrown failure (:166-176) — the check threw, which by raceCheckTimeout
(:333-351, it rejects rather than returning) includes every timeout overrun:
}catch(error){status='failed';message=errorinstanceofError ? error.message : 'Unknown error';this.failureCounters.set(pluginName,(this.failureCounters.get(pluginName)||0)+1);this.healthStatus.set(pluginName,'failed');// ...}config.autoRestart is read at :159 and nowhere else — grep -n "autoRestart" packages/core/src/health-monitor.ts returns exactly that one hit. So:
- A plugin whose check politely returns
false is restarted once failureThreshold
rounds accumulate, when autoRestart: true. - A plugin whose check throws, or hangs until
timeout, is marked failed and is
never restarted, no matter how many rounds pass or what autoRestart says.
The more severe failure mode is the one that cannot trigger recovery.
A second asymmetry sits in the same block: the catch path increments failureCounters
but does not reset successCounters, where the returned-failure path does (:148).
A plugin alternating between throwing and passing therefore accumulates successes across
its failures and can reach successThreshold on a counter the other route would have
cleared.
What is NOT claimed
Whether this is a defect or deliberate is not determined here — no comment, ADR or
test states an intent either way, and I did not search beyond this file. It is recorded
because the two routes are reachable from the same config with opposite recovery
behaviour, and nothing declares which is intended.
No documentation contradicts the current behaviour: content/docs/protocol/kernel/lifecycle.mdx
mentions autoRestart only once (:699), listing the parsed default, and never says
which failures trigger a restart. So this is a source-behaviour question, not a docs bug.
health-monitor.test.ts pins the timeout guard lifetime (#4875) but asserts nothing
about restarts on either route.
Generated by Claude Code
Found while implementing #11823 (documenting the third
checks[].nameinlifecycle.mdx). Out of that card's scope — it is a docs-only card fenced to thechecks[].nameenumeration — so this is filed rather than fixed.What was measured
packages/core/src/health-monitor.tsata1c804bc9.performHealthCheckreaches itsfailure handling by two disjoint routes, and only one of them can restart the plugin.
Returned failure (
:116-119→:146-164) — the check returnedfalseor{ status: 'unhealthy' }:Thrown failure (
:166-176) — the check threw, which byraceCheckTimeout(
:333-351, it rejects rather than returning) includes everytimeoutoverrun:config.autoRestartis read at:159and nowhere else —grep -n "autoRestart" packages/core/src/health-monitor.tsreturns exactly that one hit. So:falseis restarted oncefailureThresholdrounds accumulate, when
autoRestart: true.timeout, is markedfailedand isnever restarted, no matter how many rounds pass or what
autoRestartsays.The more severe failure mode is the one that cannot trigger recovery.
A second asymmetry sits in the same block: the catch path increments
failureCountersbut does not reset
successCounters, where the returned-failure path does (:148).A plugin alternating between throwing and passing therefore accumulates successes across
its failures and can reach
successThresholdon a counter the other route would havecleared.
What is NOT claimed
Whether this is a defect or deliberate is not determined here — no comment, ADR or
test states an intent either way, and I did not search beyond this file. It is recorded
because the two routes are reachable from the same config with opposite recovery
behaviour, and nothing declares which is intended.
No documentation contradicts the current behaviour:
content/docs/protocol/kernel/lifecycle.mdxmentions
autoRestartonly once (:699), listing the parsed default, and never sayswhich failures trigger a restart. So this is a source-behaviour question, not a docs bug.
health-monitor.test.tspins the timeout guard lifetime (#4875) but asserts nothingabout restarts on either route.
Generated by Claude Code