fix(client): treat HTTP 401/403 on the negotiation probe as auth failures, not legacy evidence (#2564)

This commit is contained in:
Felix Weinberger 2026-07-28 00:41:02 +01:00 • committed by GitHub
commit faa7e2b470
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 909 additions and 91 deletions

View file

@ -71,7 +71,15 @@ infrastructure problems. Anything the probe does not positively recognize as mod
falls back to the legacy era — provided the supported-versions list still contains a
2025-era revision; with a modern-only list `connect()` rejects with
`SdkError(EraNegotiationFailed)` instead. A network outage rejects with a typed connect
error. Probe timeouts are **transport-aware**: on **stdio** a server that does not
error. Auth statuses are another exception: an HTTP `401` or `403` rejecting the probe
is never era evidence — `connect()` rejects with a typed authorization failure (an
`SdkHttpError` with code `ClientHttpAuthentication`/`ClientHttpForbidden` naming the
status, or the transport auth flow's own error propagated unchanged) instead of
falling back — see [Protocol versions](../protocol-versions.md). This holds even for
a front that answers the probe `403` but would pass `initialize`: relying on a legacy
fallback there is a deliberate non-goal — fix the auth wall (or pass credentials),
because auth status never selects an era. A `5xx` on the probe is a server failure,
also never era evidence: `connect()` rejects with `SdkHttpError(EraNegotiationFailed)`. Probe timeouts are **transport-aware**: on **stdio** a server that does not
answer within `timeoutMs` is treated as legacy and the client falls back to `initialize`
(some legacy servers never respond to unknown pre-`initialize`
requests at all); on **HTTP** a probe timeout rejects with `SdkError(RequestTimeout)` —

View file

@ -1150,11 +1150,11 @@ try {
}
```
One qualification: this direct `instanceof` check applies under the default `'legacy'`
version negotiation. Under the probing modes (`versionNegotiation: { mode: 'auto' }`,
with or without a pin) the connect-time 401 currently surfaces wrapped as
`SdkError(SdkErrorCode.EraNegotiationFailed)` with the `UnauthorizedError` at
`error.data.cause` — unwrap before the check, as shown in the
This direct `instanceof` check works in every version-negotiation mode: under the
probing modes (`versionNegotiation: { mode: 'auto' }`, with or without a pin) the
connect-time `UnauthorizedError` also propagates unchanged from `connect()`. Older
releases wrapped it as `SdkError(SdkErrorCode.EraNegotiationFailed)` with the error at
`error.data.cause` — that unwrap is no longer needed. See the
[client OAuth guide](../clients/oauth.md).
#### `auth()` options are now `AuthOptions`