Skip to content

autoRestart never fires for a health check that throws or times out — only for one that returns a failure #11852

Description

@os-steve

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 elsegrep -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

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions