Skip to content

fix(client): put the probe's network failure on Error.cause, not data - #2703

Open
likalight wants to merge 2 commits into
modelcontextprotocol:mainfrom
likalight:fix/probe-classifier-cause-chain
Open

fix(client): put the probe's network failure on Error.cause, not data#2703
likalight wants to merge 2 commits into
modelcontextprotocol:mainfrom
likalight:fix/probe-classifier-cause-chain

Conversation

@likalight

Copy link
Copy Markdown

Fixes #2657.

The bug

SdkError's third constructor parameter is data, not ErrorOptions:

constructor(
    public readonly code: SdkErrorCode,
    message: string,
    public readonly data?: unknown
) {
    super(message);   // no options forwarded

So classifyNetworkError passing { cause: error } as the third argument
never reached Error.cause. The underlying failure was stranded at
error.data.cause, where nothing walking the standard .cause chain —
loggers, error reporters, util.inspect — will find it. Connecting to an
unreachable host surfaced an SdkError whose .cause was undefined, with
the TypeError: fetch failed and the DNS error that actually names the
problem both invisible to normal error handling.

The fix

Set cause on the instance with the same non-enumerable descriptor the
Error constructor would have produced.

I grepped the repo: probeClassifier.ts:314 was the only site passing
{ cause } into an SdkError data slot, so this is self-contained.

One thing worth your call

test/client/probeAuthSeam.test.ts had an assertion pinning the old
behaviour:

expect(((out.error as SdkError).data as { cause?: unknown }).cause).toBe(netError);

I updated it to assert .cause instead, on the reading that it was codifying
the bug rather than a contract you intend to keep. If data.cause is
load-bearing for downstream consumers, say so and I'll set both
— the
one-line alternative is to keep passing { cause: error } as data in
addition to setting the real cause. Happy to go either way.

Verification

  • Added a regression test in probeClassifier.test.ts asserting the full
    chain resolves (SdkErrorTypeError: fetch failedENOTFOUND).
    Verified it fails without the source change:
    AssertionError: expected undefined to be TypeError: fetch failed
  • packages/client: 805/805 tests pass
  • tsc --noEmit: clean
  • prettier --check: clean on all touched files

Note: pnpm lint:all fails on this branch, but it fails identically on a
clean checkout of main — a sync:snippets docs drift unrelated to this
change.

`SdkError`'s third constructor parameter is `data`, not `ErrorOptions`, so
`classifyNetworkError` passing `{ cause: error }` left `Error.cause`
undefined and stranded the underlying failure at `error.data.cause`.
Anything walking the standard `.cause` chain — loggers, error reporters,
`util.inspect` — stopped at the `SdkError` and never reached the
`TypeError: fetch failed`, nor the DNS/socket error beneath it that
actually names the failure.

Set `cause` on the instance with the same non-enumerable descriptor the
`Error` constructor would have produced.

This was the only site in the repo passing `{ cause }` into an
`SdkError` data slot.

Fixes modelcontextprotocol#2657

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@likalight
likalight requested a review from a team as a code owner August 23, 2026 09:02
@changeset-bot

changeset-bot Bot commented Aug 23, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: faba11c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 6 packages
Name Type
@modelcontextprotocol/client Patch
@modelcontextprotocol/core Patch
@modelcontextprotocol/server Patch
@modelcontextprotocol/server-legacy Patch
@modelcontextprotocol/codemod Patch
@modelcontextprotocol/core-internal Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Aug 23, 2026

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/@modelcontextprotocol/client@2703

@modelcontextprotocol/codemod

npm i https://pkg.pr.new/@modelcontextprotocol/codemod@2703

@modelcontextprotocol/core

npm i https://pkg.pr.new/@modelcontextprotocol/core@2703

@modelcontextprotocol/server

npm i https://pkg.pr.new/@modelcontextprotocol/server@2703

@modelcontextprotocol/server-legacy

npm i https://pkg.pr.new/@modelcontextprotocol/server-legacy@2703

@modelcontextprotocol/express

npm i https://pkg.pr.new/@modelcontextprotocol/express@2703

@modelcontextprotocol/fastify

npm i https://pkg.pr.new/@modelcontextprotocol/fastify@2703

@modelcontextprotocol/hono

npm i https://pkg.pr.new/@modelcontextprotocol/hono@2703

@modelcontextprotocol/node

npm i https://pkg.pr.new/@modelcontextprotocol/node@2703

commit: faba11c

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[v2] classifyNetworkError passes { cause } into SdkError's data slot, so the underlying network error never reaches Error.cause

1 participant