diff --git a/packages/core-internal/src/shared/stdio.ts b/packages/core-internal/src/shared/stdio.ts index 8bd794b87b..e90bd4cab2 100644 --- a/packages/core-internal/src/shared/stdio.ts +++ b/packages/core-internal/src/shared/stdio.ts @@ -31,7 +31,8 @@ export class ReadBuffer { } const line = this._buffer.toString('utf8', 0, index).replace(/\r$/, ''); - this._buffer = this._buffer.subarray(index + 1); + const remainder = this._buffer.subarray(index + 1); + this._buffer = remainder.length === 0 ? undefined : remainder; try { return deserializeMessage(line); diff --git a/packages/core-internal/test/shared/stdio.test.ts b/packages/core-internal/test/shared/stdio.test.ts index f8d27a4c1f..114912a6d7 100644 --- a/packages/core-internal/test/shared/stdio.test.ts +++ b/packages/core-internal/test/shared/stdio.test.ts @@ -1,3 +1,4 @@ +import { vi } from 'vitest'; import { ReadBuffer, STDIO_DEFAULT_MAX_BUFFER_SIZE } from '../../src/shared/stdio'; import type { JSONRPCMessage } from '../../src/types/index'; @@ -156,3 +157,17 @@ describe('buffer size limit', () => { expect(readBuffer.readMessage()).not.toBeNull(); }); }); + +test('should clear backing allocation when final byte is consumed', () => { + const readBuffer = new ReadBuffer(); + const largePayload = 'x'.repeat(1024); + readBuffer.append(Buffer.from(JSON.stringify({ jsonrpc: '2.0', method: largePayload }) + '\n')); + expect(readBuffer.readMessage()).toEqual({ jsonrpc: '2.0', method: largePayload }); + + const appendSpy = vi.spyOn(Buffer, 'concat'); + const nextChunk = Buffer.from(JSON.stringify(testMessage) + '\n'); + readBuffer.append(nextChunk); + expect(appendSpy).not.toHaveBeenCalled(); + expect(readBuffer.readMessage()).toEqual(testMessage); + appendSpy.mockRestore(); +});