Skip to content

npm run ci cannot pass: two pre-existing unhandled rejections in inspectorClient.test.ts abort the gate at coverage #1947

Description

@cliffhall

Summary

npm run ci — the mandatory pre-push gate — exits 1 on a clean v2/main tree, and does so in a way that looks like success. Two unhandled promise rejections in clients/web/src/test/integration/mcp/inspectorClient.test.ts make vitest fail the run even though every test passes, which aborts the chain at coverage and silently skips the last three stages.

Reproduce

From a clean v2/main checkout with no local changes:

npm run ci; echo "EXIT=$?"
 Test Files  313 passed (313)
      Tests  4812 passed (4812)
     Errors  2 errors
...
=============================== Coverage summary ===============================
Statements   : 98.62% ( 10955/11108 )
...
EXIT=1

Both rejections are the same shape:

⎯⎯⎯⎯ Unhandled Rejection ⎯⎯⎯⎯⎯
SdkError: Connection closed
 ❯ Client._onclose .../shared/protocol.ts:839:23
 ❯ StreamableHTTPClientTransport.close .../client/streamableHttp.ts:890:18
 ❯ MessageTrackingTransport.close ../../core/mcp/messageTrackingTransport.ts:110:31
 ❯ InspectorClient.disconnect ../../core/mcp/inspectorClient.ts:2092:29
 ❯ src/test/integration/mcp/inspectorClient.test.ts:2264:21

The second is identical from :2382. Reported against the tests "should not dispatch progressNotification events when progress is disabled" and "should complete when timeout and resetTimeoutOnProgress are set (options passed through)" — a teardown race in disconnect, where the close rejection is never awaited.

Reproduces standalone too:

cd clients/web && npx vitest run --project=integration src/test/integration/mcp/inspectorClient.test.ts
#  Tests  140 passed (140)
#  Errors  2 errors

Why this is worse than a flake

ci is validatecoverageverify:build-gatesmoke → Storybook, chained on success. Because coverage exits non-zero, verify:build-gate, smoke, and ci:storybook never run. The failure is easy to misread: the last thing printed is a coverage summary with 100% of tests passing, so it reads as a completed run rather than an aborted one.

That defeats the guarantee AGENTS.md makes for the command — "a true superset of GitHub CI, and passing it locally means CI's gates will pass". Right now it cannot pass, and anyone who eyeballs the tail concludes it did while a third of the gate was skipped. Two of us hit exactly this on #1946 and #1945; both needed the last three stages run by hand afterwards (they pass).

Scope

  • Not caused by any current PR — confirmed by stashing all local changes on two separate branches and reproducing on the clean baseline.
  • Deterministic on this machine (macOS 26.6 / Node v25.9.0), 3+ runs. Whether CI's Linux runners hit it is unverified; GitHub CI is currently green, which suggests it may be environment-sensitive — that's worth establishing as part of the fix.

Suggested direction

Fix the teardown race so disconnect() doesn't leave an unawaited rejecting close (either await the transport close or attach a handler in InspectorClient.disconnect / MessageTrackingTransport.close). Swallowing it at the vitest config level would restore a green exit code while leaving a real unhandled rejection in the runtime path, so it isn't the right lever.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingv2Issues and PRs for v2

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions