From a57d9b933680719cbc723b9fb2ae27bc7b455f17 Mon Sep 17 00:00:00 2001 From: Ella Hathaway Date: Tue, 1 Sep 2026 18:51:51 -0700 Subject: [PATCH 1/2] Keep claim timestamps consistent Use one captured timestamp for notification claim payloads and filenames so valid claims never block unrelated suppressions as malformed. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 06ff4fb7-3a0a-4a61-8653-8345b646778c --- .../test/outdatedCliSuppressionStore.test.ts | 25 +++++++++++++++++++ .../src/utils/outdatedCliSuppressionStore.ts | 10 +++++--- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/extension/src/test/outdatedCliSuppressionStore.test.ts b/extension/src/test/outdatedCliSuppressionStore.test.ts index b1c0dfbb88a..e13dc4c1df4 100644 --- a/extension/src/test/outdatedCliSuppressionStore.test.ts +++ b/extension/src/test/outdatedCliSuppressionStore.test.ts @@ -3,6 +3,7 @@ import { spawn } from 'child_process'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; +import * as sinon from 'sinon'; import { FileSystemOutdatedCliSuppressionStore } from '../utils/outdatedCliSuppressionStore'; suite('outdatedCliSuppressionStore', () => { @@ -30,6 +31,30 @@ suite('outdatedCliSuppressionStore', () => { ['/cli/a\u000013.5.0', '/cli/b\u000013.5.0']); }); + test('uses one timestamp for a claim marker and payload', async () => { + const nowStub = sinon.stub(Date, 'now'); + nowStub.onFirstCall().returns(1_000); + nowStub.returns(1_001); + const first = new FileSystemOutdatedCliSuppressionStore(directory); + const second = new FileSystemOutdatedCliSuppressionStore(directory); + const claim = await first.tryClaimNotification('/cli/a\u000013.5.0'); + assert.ok(claim); + + try { + const storageDirectory = path.join(directory, 'outdated-cli-suppressions'); + const claimMarker = fs.readdirSync(storageDirectory) + .find(entry => entry.startsWith('notification-claim-')); + assert.ok(claimMarker?.startsWith('notification-claim-1000-')); + + await second.add('/cli/b\u000013.5.0'); + assert.deepStrictEqual(await second.readAll(), ['/cli/b\u000013.5.0']); + } + finally { + await claim.release(); + nowStub.restore(); + } + }); + test('serializes a suppression written after the final notification check', async () => { const first = new FileSystemOutdatedCliSuppressionStore(directory); const second = new FileSystemOutdatedCliSuppressionStore(directory); diff --git a/extension/src/utils/outdatedCliSuppressionStore.ts b/extension/src/utils/outdatedCliSuppressionStore.ts index d1ade9071a3..8ddeeda57f7 100644 --- a/extension/src/utils/outdatedCliSuppressionStore.ts +++ b/extension/src/utils/outdatedCliSuppressionStore.ts @@ -56,7 +56,7 @@ export class FileSystemOutdatedCliSuppressionStore implements OutdatedCliSuppres notificationKey, processId: process.pid, createdAt, - }); + }, createdAt); let released = false; const claim: OutdatedCliNotificationClaim = { isValid: () => isLeaseCurrent(createdAt), @@ -178,9 +178,13 @@ export class FileSystemOutdatedCliSuppressionStore implements OutdatedCliSuppres } } - private async _publishMarker(prefix: string, value: unknown): Promise { + private async _publishMarker( + prefix: string, + value: unknown, + createdAt = Date.now(), + ): Promise { await mkdir(this._directoryPath, { recursive: true }); - const generation = `${Date.now()}-${process.pid}-${markerSequence++}`; + const generation = `${createdAt}-${process.pid}-${markerSequence++}`; const fileName = `${prefix}${generation}${markerFileSuffix}`; const temporaryPath = path.join(this._directoryPath, `.${fileName}.tmp`); const finalPath = path.join(this._directoryPath, fileName); From 8430ca051f926e71396168146cd655277ce7e8ea Mon Sep 17 00:00:00 2001 From: Ella Hathaway Date: Tue, 1 Sep 2026 19:19:52 -0700 Subject: [PATCH 2/2] Always restore the claim clock stub Enter cleanup before claim creation so failures cannot leak the global Date.now stub into later extension tests. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 06ff4fb7-3a0a-4a61-8653-8345b646778c --- .../src/test/outdatedCliSuppressionStore.test.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/extension/src/test/outdatedCliSuppressionStore.test.ts b/extension/src/test/outdatedCliSuppressionStore.test.ts index e13dc4c1df4..1d4d2b79ff1 100644 --- a/extension/src/test/outdatedCliSuppressionStore.test.ts +++ b/extension/src/test/outdatedCliSuppressionStore.test.ts @@ -4,7 +4,10 @@ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; import * as sinon from 'sinon'; -import { FileSystemOutdatedCliSuppressionStore } from '../utils/outdatedCliSuppressionStore'; +import { + FileSystemOutdatedCliSuppressionStore, + OutdatedCliNotificationClaim, +} from '../utils/outdatedCliSuppressionStore'; suite('outdatedCliSuppressionStore', () => { let directory: string; @@ -37,10 +40,11 @@ suite('outdatedCliSuppressionStore', () => { nowStub.returns(1_001); const first = new FileSystemOutdatedCliSuppressionStore(directory); const second = new FileSystemOutdatedCliSuppressionStore(directory); - const claim = await first.tryClaimNotification('/cli/a\u000013.5.0'); - assert.ok(claim); + let claim: OutdatedCliNotificationClaim | undefined; try { + claim = await first.tryClaimNotification('/cli/a\u000013.5.0'); + assert.ok(claim); const storageDirectory = path.join(directory, 'outdated-cli-suppressions'); const claimMarker = fs.readdirSync(storageDirectory) .find(entry => entry.startsWith('notification-claim-')); @@ -50,7 +54,7 @@ suite('outdatedCliSuppressionStore', () => { assert.deepStrictEqual(await second.readAll(), ['/cli/b\u000013.5.0']); } finally { - await claim.release(); + await claim?.release(); nowStub.restore(); } });