fix: cancellable compat probes and a validated budget (#2319 round 16)

Two real bugs and two stale rationales.

The OIDC fallback probes were issued with `{ headers }` and no signal,
so a caller that aborted after the initial 404 got a timeout reported
in place of its own cancellation, while the probe ran on to its own
deadline. The probes now carry the caller's signal — via the shared
`callerSignalOf`, so the precedence rule has one definition — a
pre-aborted caller is not probed at all, and an abort is rethrown
rather than folded back into the preceding response.

`Math.max(0, Math.round(timeoutMs))` let NaN and Infinity through.
`setTimeout(fn, NaN)` fires immediately, and a delay above 2**31-1
overflows a 32-bit signed int and falls back to 1ms — so a caller
asking for a very large budget, or one whose arithmetic produced NaN,
got an immediate OAuth timeout. `normalizeBudget` falls back to the
default on non-finite and clamps to the timer's range; the route's
envelope-supplied deadline gets the same clamp, but ignores a
non-finite value outright since a malformed deadline reads safest as
no deadline.

The wire-marker docblock and the matching route test still said a
backend deadline is the only one on the transport-internal discovery
path, which round 11 changed. Both now give the real reason the marker
matters: both ends run the same budget, and it is the backend winning
the race — a backgrounded tab throttling setTimeout — that must not
decide whether the error names its endpoint.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YFnM6RqB4nXqNysgUYmjmq
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-09-11 06:06:36 -04:00
commit e234343702

View file

@ -1256,10 +1256,15 @@ export function createRemoteApp(
// the regression `exemptMcpEndpoint` exists to prevent on the client, and
// reporting it as an OAuth timeout besides (Copilot). No deadline in the
// envelope means no timer at all.
// Clamped to what `setTimeout` can schedule: past 2**31-1 the delay
// overflows and Node falls back to 1ms, turning an over-large budget into
// an immediate timeout. Non-finite is ignored outright rather than
// defaulted — an envelope carrying `NaN` is a malformed request, and the
// safe reading of a malformed deadline is "no deadline".
const deadlineMs =
typeof requestedTimeoutMs === "number" &&
Number.isFinite(requestedTimeoutMs)
? Math.max(0, Math.round(requestedTimeoutMs))
? Math.min(2_147_483_647, Math.max(0, Math.round(requestedTimeoutMs)))
: undefined;
const timer =
deadlineMs === undefined