Repository navigation
Description
What problem does this solve?
#1947 made npm run ci unpassable: two tests fired client.callTool(...) without holding the promise, disconnect() rejected the pending request with Connection closed, and the unhandled rejection failed the whole vitest run — aborting the chain at coverage and silently skipping verify:build-gate, smoke, and Storybook. #1958 fixes those two call sites.
What it does not do is stop the next one. A floating promise is invisible at review time: the call looks like every awaited call minus four characters, and the failure it causes surfaces in a different test, in a different file, as a stack that points at SDK internals. #1947 took a full investigation to attribute; the fix was two lines.
@typescript-eslint/no-floating-promises catches the class outright. It is not currently enabled anywhere — all five ESLint scopes (clients/{web,cli,tui,launcher} and the root core/ + shared gate) extend tseslint.configs.recommended, which is the non-type-aware set. The rule needs type information, so enabling it means turning on parserOptions.projectService for the linted globs.
Measured surface
Probed against clients/web with the rule on (recommendedTypeChecked + projectService), on top of #1958:
| Bucket | Violations |
|---|---|
*.stories.tsx (play functions) |
73 |
| Tests | 5 |
server/ production code |
2 |
| Total | 80 |
So the production surface is ~2 sites, and the overwhelming bulk is Storybook play functions — almost certainly un-awaited userEvent.* calls, which are mechanical to fix and arguably worth fixing on their own (an un-awaited userEvent is a real source of interaction-test flake). The other three clients and core/ are unmeasured; this probe covered web only.
The 80 are why this wasn't folded into #1958 — it is a cleanup pass, not a config toggle.
Proposed approach
- Turn on
projectServiceper ESLint scope and enable onlyno-floating-promises(not the wholerecommendedTypeCheckedset — that is a much larger and separate argument). - Measure the remaining four scopes the same way.
- Fix the sites. For a deliberately un-awaited call, the fix is to hold and settle the promise —
clients/web/src/test/integration/mcp/inspectorClient.test.tshas asettleInFlight()helper from fix: settle the deliberately in-flight tool calls in the progress tests #1958 that documents the pattern and the reasoning. - Land the rule as
erroronce the count is zero.
Things to decide during the work
- Scope. Test/story globs only, or production code too? Production is where an unhandled rejection actually hurts a user, and it is only ~2 sites in web — probably worth including rather than deferring.
- Lint runtime. Type-aware linting is materially slower, and
lintruns insidevalidate, the fast inner-loop check. Worth timing before/after; if the hit is bad, scopingprojectServiceto the globs that need it is the lever. voidas an escape hatch. The rule acceptsvoid somePromise()as "deliberately ignored". That is strictly better than nothing (it is visible at review time), but it still discards the rejection. Prefer holding and settling the promise where a rejection is actually possible, and reservevoidfor calls that genuinely cannot reject.
Additional context
Discovered while fixing #1947 (PR #1958) — flagged there as a follow-up rather than folded in.
Activity
cliffhall commentedon Aug 10, 2026
Triage: Priority Medium (total 6)
await) is exactly what failed in npm run ci cannot pass: two pre-existing unhandled rejections in inspectorClient.test.ts abort the gate at coverage #1947Milestone v2.3.0, not the current v2.2.0 — that closes 2026-08-12 and this is an ~80-site cleanup, so it plainly won't make it.
Board: #28, Status
Todo— filed through the documented create flow by a maintainer, so filing it is the approval.chore(lint): enable no-floating-promises in all five ESLint scopes