diff --git a/packages/core/src/shared/protocol.ts b/packages/core/src/shared/protocol.ts index 361bd6fc7c..da7fabc12b 100644 --- a/packages/core/src/shared/protocol.ts +++ b/packages/core/src/shared/protocol.ts @@ -1010,7 +1010,10 @@ export abstract class Protocol { // A notification can only be debounced if it's in the list AND it's "simple" // (i.e., has no parameters and no related request ID or related task that could be lost). const canDebounce = - debouncedMethods.includes(notification.method) && !notification.params && !options?.relatedRequestId && !options?.relatedTask; + debouncedMethods.includes(notification.method) && + !notification.params && + options?.relatedRequestId == null && + !options?.relatedTask; if (canDebounce) { // If a notification of this type is already scheduled, do nothing. diff --git a/packages/core/test/shared/protocol.test.ts b/packages/core/test/shared/protocol.test.ts index 619e09376a..12f5f380a1 100644 --- a/packages/core/test/shared/protocol.test.ts +++ b/packages/core/test/shared/protocol.test.ts @@ -768,6 +768,22 @@ describe('protocol tests', () => { expect(sendSpy).toHaveBeenCalledWith(expect.any(Object), { relatedRequestId: 'req-2' }); }); + it('should NOT debounce a notification that has relatedRequestId 0 (regression for #2117)', async () => { + // ARRANGE: id=0 is a valid JSON-RPC RequestId (and the first id a Protocol uses). + // The truthy guard `!options?.relatedRequestId` treated 0 as absent and + // incorrectly debounced these related notifications. Pin the fixed behaviour. + protocol = new TestProtocolImpl({ debouncedNotificationMethods: ['test/debounced_with_options'] }); + await protocol.connect(transport); + + // ACT + await protocol.notification({ method: 'test/debounced_with_options' }, { relatedRequestId: 0 }); + await protocol.notification({ method: 'test/debounced_with_options' }, { relatedRequestId: 0 }); + + // ASSERT: both calls must reach the transport (no coalescing). + expect(sendSpy).toHaveBeenCalledTimes(2); + expect(sendSpy).toHaveBeenCalledWith(expect.any(Object), { relatedRequestId: 0 }); + }); + it('should clear pending debounced notifications on connection close', async () => { // ARRANGE protocol = new TestProtocolImpl({ debouncedNotificationMethods: ['test/debounced'] });