perf(serve): gate HealthEndpoint off by default in stdio mode#652
perf(serve): gate HealthEndpoint off by default in stdio mode#652
Conversation
Implements issue #648. Per-instance cost of the HealthEndpoint HTTP listener is unnecessary for stdio MCP sessions (1 FD, ~200 KB heap) but remains useful in daemon modes (http / both). This change: - Adds src/utils/health-endpoint-gating.ts with a pure resolver: OPENCHROME_HEALTH_ENDPOINT=1/true -> on, =0/false -> off, other values fall through to the transport-mode default (http/both -> on, stdio -> off). - Wires the resolver at HealthEndpoint construction in src/index.ts. When disabled, the instance is null and teardown is optional-chained. - Preserves daemon-mode behaviour byte-for-byte (existing integrators continue to see /health on the same port). - Emits one stderr log line per startup declaring enabled/disabled state. Tests: - tests/utils/health-endpoint-gating.test.ts — 8 resolver cases. - tests/integration/health-endpoint-gating.test.ts — 7 scenarios including graceful-shutdown audit (no TypeError with null endpoint). Documentation: - README.md env-var table entry for OPENCHROME_HEALTH_ENDPOINT. - docs/releases/v1.10.3.md staging notes bullet. Benchmark (n=20 spawns, scripts/bench-health-endpoint.mjs): - stdio baseline (endpoint=1): 31 FDs per instance, RSS median 122 MB. - stdio patched (endpoint=0 default): 30 FDs per instance, RSS median 122 MB. - FD delta: -1 per stdio instance (deterministic, matches expectation). - http-patched: 32 FDs (unchanged from baseline). Rollback: OPENCHROME_HEALTH_ENDPOINT=1 restores the stdio listener.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 903e913c9b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| console.error(`[SelfHealing] HealthEndpoint: enabled (port=${healthPort}, bind=${healthBind}, mode=${transportMode})`); | ||
| healthEndpoint.start().catch((err: unknown) => { | ||
| console.error('[SelfHealing] HealthEndpoint start failed:', err); |
There was a problem hiding this comment.
Log enabled state only after listener startup succeeds
The code emits HealthEndpoint: enabled before healthEndpoint.start() has actually bound the socket. If startup fails (for example, EADDRINUSE on the default port when another instance is running), this line still claims the endpoint is enabled even though no listener is active. That makes the new startup state log unreliable in a realistic daemon deployment scenario and can mislead health/ops checks that consume this signal.
Useful? React with 👍 / 👎.
The stdio-gating change correctly stopped binding the health listener in unnecessary cases, but the disabled log always claimed stdio mode even when operators had explicitly forced the endpoint off in HTTP mode. This narrows the message to the actual reason so stderr remains trustworthy during rollout and debugging. Constraint: Startup logs must stay on stderr and preserve the existing gating behavior Rejected: Leave the misleading message in place | would make forced-off HTTP cases look like transport-default disablement Confidence: high Scope-risk: narrow Reversibility: clean Directive: If more health-endpoint gating states are added, update the operator log strings and integration assertions together Tested: npx jest tests/integration/health-endpoint-gating.test.ts --runInBand Not-tested: Fresh GitHub Actions matrix after branch push
The stdio health-endpoint fix does not need to stage a future release note in the feature branch, and carrying a v1.10.3 draft here collides with the other open performance PRs. Dropping the staging note keeps the branch mergeable and leaves final release documentation to the actual release-prep step. Constraint: Open performance PRs must merge cleanly into develop without competing release-note drafts Rejected: Keep the v1.10.3 staging file here | creates needless conflicts with sibling PRs touching the same placeholder doc Confidence: high Scope-risk: narrow Reversibility: clean Directive: Do not stage shared future release-note files in issue-specific PR branches unless the release owner explicitly asks for it Tested: git diff --name-only upstream/develop...HEAD Not-tested: Re-run GitHub CI after push
Closes #648.
Summary
HealthEndpoint's HTTP listener served every stdio MCP instance with no consumer. This PR gates it off by default in stdio mode (and preserves daemon-mode behaviour) behind a pure resolver with an explicit opt-in via `OPENCHROME_HEALTH_ENDPOINT`.
Acceptance criteria (from issue #648 §4)
Pre-merge checklist (from issue #648 §5)
5.1 Static / hygiene
5.2 Unit: pure resolver (8/8 green)
5.3 Integration: real port binding (7/7 green)
5.4 Memory measurement gate
5.5 Cross-platform CI
5.6 Operator UX regression
Benchmark results (from issue §5.4)
Per-stdio-instance FD saving: -1 FD. With 80 concurrent stdio instances this reclaims 80 FDs that were bound listeners with no consumer.
Files
Rollback
Immediate operator-side: `OPENCHROME_HEALTH_ENDPOINT=1` in environment, no restart needed on next spawn. No config migration, no data to undo. Full revert is a single `git revert`.