fix(benchmark): stop MCP startup deadlock — drop script-only runners from the calibration library barrel#2
Merged
Conversation
…from the calibration library barrel
The calibration barrel (packages/benchmark/calibration/index.ts) re-exported
the script-only runners (runCalibration, selectModeFromArgv,
runProductionCalibration, runProductionFromCli) alongside its library-safe
oracle/stats seams. Those runner modules are script-only — top-level await +
FS writes, per the §2.2 layer-contract banner in calibrate-gates-production.ts.
@prd-gen/mcp-server imports this barrel (via build-conclude-opts) only for the
library-safe oracle dispatch (invokeOracle / OracleUnavailableError). But
esbuild, bundling the server, must initialise every statically-reachable
module — so the script island's top-level await was awaited at server startup
and never settled. The MCP `initialize` handshake never completed because
server.connect() was never reached: the plugin's MCP server hung on every launch.
Import chain (esbuild metafile):
mcp-server/index -> pipeline-tools -> build-conclude-opts
-> @prd-gen/benchmark/calibration (barrel) -> calibrate-gates-production (TLA island)
Fix: stop the library barrel from re-exporting the script-only runners. They
have no barrel consumers — the CLIs and their tests already import them
directly from their own modules — so this is non-breaking and simply honours
the §2.2 script/library boundary those modules already document. The
library-safe oracle/stats seams the server actually needs are unchanged.
Verification:
- esbuild metafile: script island no longer in the server graph
- regenerated bundle (mcp-server/index.js) has zero top-level await
- MCP server now answers `initialize` immediately (was: deadlock)
- pnpm vitest packages/benchmark packages/mcp-server: 339 passed
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The plugin's MCP server (
prd-gen) deadlocks on every launch — it never answers the MCPinitializehandshake, so the server fails to come up under Claude Code. Node printsWarning: Detected unsettled top-level awaitand noevidence.dbis created.Root cause
The calibration library barrel
packages/benchmark/calibration/index.tsre-exports the script-only runners next to its library-safe oracle/stats seams:runCalibration,selectModeFromArgv(from./calibrate-gates.js)runProductionCalibration(from./calibrate-gates-production.js)runProductionFromCli(from./calibrate-gates-production-cli.js)Those modules are script-only — top-level await + FS writes — exactly as their own §2.2 layer-contract banner in
calibrate-gates-production.tsdocuments.@prd-gen/mcp-serverimports this barrel (viabuild-conclude-opts.ts) only for the library-safe oracle dispatch (invokeOracle/OracleUnavailableError/OracleInput). But esbuild, bundling the server, must initialise every statically-reachable module — so the script island's top-level await runs at server startup and never settles, soserver.connect()is never reached.Authoritative import chain (esbuild metafile):
Fix
Stop the library barrel from re-exporting the script-only runners. They have no barrel consumers — the CLIs and their tests already import them directly from their own modules (e.g.
../calibrate-gates.js), so this is non-breaking. It simply honours the §2.2 script/library boundary those modules already declare. The library-safe oracle/stats seams the server actually uses are untouched.Net diff: the barrel source + the regenerated committed bundle (
mcp-server/index.js).packages/benchmark/distis gitignored.Verification
calibrate-gates*) is no longer in the server graph.awaitremain inmcp-server/index.js.initializeimmediately —{"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{"listChanged":true}},"serverInfo":{"name":"prd-gen","version":"0.1.0"}},...}pnpm vitest run packages/benchmark packages/mcp-server→ 339 passed.Follow-up (not in this PR)
Separately, the published plugin ships no
node_modules, and the bundle externalisesajv(+ its dynamicajv/dist/runtime/*requires) andbetter-sqlite3, so a freshclaude plugin installcan't launch the server until deps are installed/hoisted. Worth addressing in a packaging PR (ship a postinstall, or a flatnode_modules, or inline the validator).🤖 Generated with Claude Code