fix(client): abort legacy SSE reconnect chain when the originating request times out - #2616
fix(client): abort legacy SSE reconnect chain when the originating request times out#2616claude[bot] wants to merge 17 commits into
Code review found 1 important issue
Found 5 candidates, confirmed 4. See review comments for details.
Details
| Severity | Count |
|---|---|
| 🔴 Important | 1 |
| 🟡 Nit | 1 |
| 🟣 Pre-existing | 2 |
| Severity | File:Line | Issue |
|---|---|---|
| 🔴 Important | packages/core-internal/src/shared/protocol.ts:1183-1199 |
maxTotalTimeout settlement leaves the per-leg timer armed; it re-fires cancel() and POSTs a duplicate notifications/canc |
| 🟡 Nit | packages/client/src/client/streamableHttp.ts:1200-1213 |
terminateSession is the last surviving unguarded onerror site of the sweep this PR finishes |
| 🟣 Pre-existing | packages/client/src/client/streamableHttp.ts:964-993 |
Resumed requests: replayed progress notifications carry the original progressToken — onprogress/resetTimeoutOnProgress a |
Annotations
Check failure on line 1199 in packages/core-internal/src/shared/protocol.ts
claude / Claude Code Review
maxTotalTimeout settlement leaves the per-leg timer armed; it re-fires cancel() and POSTs a duplicate notifications/cancelled on legacy sessions
The c1b55eb reroute of the maxTotalTimeout settlement through cancel() leaves the per-leg timeout timer armed (_resetTimeout's over-budget branch deletes the _timeoutInfo entry and throws without clearTimeout, and cancel() never sets responseReceived), so when the orphaned timer later fires, cancel() re-runs in full and POSTs a second notifications/cancelled for the same requestId on any legacy-era (2025-11-25) transport. One-line fix: clearTimeout(info.timeoutId) in _resetTimeout's maxTotalTime
Check warning on line 1213 in packages/client/src/client/streamableHttp.ts
claude / Claude Code Review
terminateSession is the last surviving unguarded onerror site of the sweep this PR finishes
Pre-existing line, but it is the last surviving unguarded `onerror` site of the discipline sweep this PR's c1b55eb claims to finish: `terminateSession`'s catch (`streamableHttp.ts:1264`) does `this.onerror?.(error as Error); throw error;` with no intentional-abort guard, while its DELETE fetch is issued with `signal: this._abortController?.signal` alone — the exact one-signal shape this PR just fixed at the POST-path catch at line 1210. `transport.close()` landing mid-DELETE (or `terminateSessio
Check notice on line 993 in packages/client/src/client/streamableHttp.ts
claude / Claude Code Review
Resumed requests: replayed progress notifications carry the original progressToken — onprogress/resetTimeoutOnProgress are dead and each event fires a spurious unknown-token onerror
Pre-existing issue (inbound twin of the resumed-cancel id asymmetry already scoped in 473bb37): `_handleSseStream` remaps `replayMessageId` onto responses only, so replayed `notifications/progress` on a resumed stream carry the ORIGINAL request's progressToken — and since a `resumptionToken` send never POSTs the fresh id, the caller's `onprogress` (registered under the fresh messageId) never fires, `resetTimeoutOnProgress` never resets, and every progress event surfaces a spurious "unknown token