fix: read the list error via useSyncExternalStore; clear correlation maps

Two fixes from Copilot's review of #1953.

useManagedListError re-synced React state from the `state` prop inside a
useEffect, which renders one frame carrying the PREVIOUS store's error
after `state` changes (switching servers) before the effect corrects it —
the pattern AGENTS.md forbids. useSyncExternalStore reads the snapshot
during render, so a store swap lands in the same frame, and it also
closes the window where an error recorded between render and subscribe
was missed. The snapshot returns the stored Error instance (or null), so
it is referentially stable as the API requires.

The `markResponseRejected` correlation maps were never cleared. Ids of
requests that never got a response — a timeout, a dropped connection —
are the only entries trackResponse can't remove, so they accumulated
across reconnects. Cleared on the start-clean connect path, per the
convention documented on resetSessionState (one route out, `onerror`
with no `onclose`, tears down nothing).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SW1p8E2uiyyLx4RKwrwSrt
This commit is contained in:
cliffhall 2026-08-08 17:41:27 -04:00
commit a6f233eb6a

View file

@ -1581,6 +1581,17 @@ export class InspectorClient extends InspectorClientEventTarget {
this.clearReceiverTasks();
this.resetSubscriptionStream();
this.cancelledTaskIds.clear();
// Correlation data is per-session: JSON-RPC ids don't survive it, and
// MessageLogState drops its entries on disconnect, so anything left here
// could only point at an entry that no longer exists. Clearing also
// releases the ids of requests that never got a response (a timeout, a
// dropped connection) — the only entries `trackResponse` can't remove, so
// without this they accumulate across reconnects. Cleared here on the
// start-clean path rather than in `disconnect()` for the reason documented
// above: one route out (`onerror` with no `onclose`) tears down nothing
// (#1953).
this.outboundRequestMethods.clear();
this.lastAnsweredRequestByMethod.clear();
for (const [, controller] of this.taskInputAbortControllers) {
controller.abort(new Error("Connection ended"));
}