From c23b9ab0166fb217579e4d863e15c048415e4bdf Mon Sep 17 00:00:00 2001 From: Laxman Reddy Aileni Date: Wed, 15 Jul 2026 21:42:58 +0000 Subject: [PATCH] fix: stop pinned context leaking to new chat windows addPinnedContext aliased the shared module-level DEFAULT_PINNED_CONTEXT array and then mutated it with unshift/push. Pinning context in one tab therefore polluted the default, so every new chat window inherited that pinned item and it could not be removed. getPinnedContext also returned the shared default array directly. Copy the default array in both places so pinning in a tab no longer mutates the shared default. Adds a regression test. --- .../agenticChat/tools/chatDb/chatDb.test.ts | 27 +++++++++++++++++++ .../agenticChat/tools/chatDb/chatDb.ts | 9 +++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/chatDb/chatDb.test.ts b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/chatDb/chatDb.test.ts index 2c0e07baff..48007a58ca 100644 --- a/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/chatDb/chatDb.test.ts +++ b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/chatDb/chatDb.test.ts @@ -65,6 +65,33 @@ describe('ChatDatabase', () => { sinon.restore() }) + describe('pinned context', () => { + beforeEach(async () => { + await chatDb.databaseInitialize(0) + }) + + it('does not mutate the shared DEFAULT_PINNED_CONTEXT when pinning in a tab', () => { + const defaultLengthBefore = util.DEFAULT_PINNED_CONTEXT.length + const customContext = { id: 'custom-folder-ctx', command: 'myFolder', label: 'folder' } as any + + // Create the tab first (with tabContext but no pinnedContext yet) so that + // addPinnedContext initializes pinnedContext from the shared default — the + // code path that previously aliased and then mutated DEFAULT_PINNED_CONTEXT. + chatDb.setRules('tab-1', { folders: {}, rules: {} } as any) + chatDb.addPinnedContext('tab-1', customContext) + + // The shared module-level default must not be polluted by pinning in a tab, + // otherwise every new chat window would inherit the custom pinned item. + assert.strictEqual(util.DEFAULT_PINNED_CONTEXT.length, defaultLengthBefore) + assert.ok(!util.DEFAULT_PINNED_CONTEXT.some((c: any) => c.id === 'custom-folder-ctx')) + + // A brand-new tab must only receive the default pinned context, not the item + // that was pinned in a different tab. + const freshTabPinned = chatDb.getPinnedContext('tab-2-fresh') + assert.ok(!freshTabPinned.some((c: any) => c.id === 'custom-folder-ctx')) + }) + }) + describe('replaceWithSummary', () => { it('should create a new history with summary message', async () => { await chatDb.databaseInitialize(0) diff --git a/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/chatDb/chatDb.ts b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/chatDb/chatDb.ts index b0dd06280f..de9b63f999 100644 --- a/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/chatDb/chatDb.ts +++ b/server/aws-lsp-codewhisperer/src/language-server/agenticChat/tools/chatDb/chatDb.ts @@ -269,7 +269,9 @@ export class ChatDatabase { const historyId = this.getOrCreateHistoryId(tabId) if (historyId) { const tab = collection.findOne({ historyId }) - return tab?.tabContext?.pinnedContext || DEFAULT_PINNED_CONTEXT + // Return a copy of the default so callers cannot mutate the shared + // module-level DEFAULT_PINNED_CONTEXT array. + return tab?.tabContext?.pinnedContext || [...DEFAULT_PINNED_CONTEXT] } } return [] @@ -313,7 +315,10 @@ export class ChatDatabase { tab.tabContext = {} } if (!tab.tabContext.pinnedContext) { - tab.tabContext.pinnedContext = DEFAULT_PINNED_CONTEXT + // Use a copy: DEFAULT_PINNED_CONTEXT is a shared module-level array, + // and the unshift/push below would otherwise mutate it, polluting the + // default pinned context for every new tab/chat window. + tab.tabContext.pinnedContext = [...DEFAULT_PINNED_CONTEXT] } // Only add context item if its not already in this tab's pinned context if (!tab.tabContext.pinnedContext.find(c => c.id === context.id)) {