From c790e077602cbd8b16b40c31cc0b7c22f0377762 Mon Sep 17 00:00:00 2001 From: Zentrik Date: Tue, 6 Jan 2026 23:11:23 +0000 Subject: [PATCH 1/8] no config debug: Fix error when multiple vscode windows are open Should fix https://github.com/microsoft/vscode-python-debugger/issues/916, but getting this installed locally seemed sufficiently complicated that I gave up. Fix is: 1. Only watch a specific endpoint file 2. Make sure this endpoint file is unique (I imagine the workspace file path can be non unique across vscode windows) --- src/extension/noConfigDebugInit.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/extension/noConfigDebugInit.ts b/src/extension/noConfigDebugInit.ts index 23ed3790..bb5e3f62 100644 --- a/src/extension/noConfigDebugInit.ts +++ b/src/extension/noConfigDebugInit.ts @@ -8,6 +8,7 @@ import { DebugSessionOptions, Disposable, GlobalEnvironmentVariableCollection, + env, l10n, RelativePattern, workspace, @@ -49,13 +50,15 @@ export async function registerNoConfigDebug( return Promise.resolve(new Disposable(() => {})); } - // create a stable hash for the workspace folder, reduce terminal variable churn + // Create unique hash based on workspace and VS Code session ID const hash = crypto.createHash('sha256'); hash.update(workspaceString.toString()); + hash.update(env.sessionId); const stableWorkspaceHash = hash.digest('hex').slice(0, 16); const tempDirPath = path.join(extPath, '.noConfigDebugAdapterEndpoints'); - const tempFilePath = path.join(tempDirPath, `endpoint-${stableWorkspaceHash}.txt`); + const endpointFilename = `endpoint-${stableWorkspaceHash}.txt`; + const tempFilePath = path.join(tempDirPath, endpointFilename); // create the temp directory if it doesn't exist if (!fs.existsSync(tempDirPath)) { @@ -92,8 +95,8 @@ export async function registerNoConfigDebug( 'Enables use of [no-config debugging](https://github.com/microsoft/vscode-python-debugger/wiki/No%E2%80%90Config-Debugging), `debugpy `, in the terminal.', ); - // create file system watcher for the debuggerAdapterEndpointFolder for when the communication port is written - const fileSystemWatcher = createFileSystemWatcher(new RelativePattern(tempDirPath, '**/*.txt')); + // create file system watcher for the debugger adapter endpoint for when the communication port is written + const fileSystemWatcher = createFileSystemWatcher(new RelativePattern(tempDirPath, endpointFilename)); const fileCreationEvent = fileSystemWatcher.onDidCreate(async (uri) => { sendTelemetryEvent(EventName.DEBUG_SESSION_START, undefined, { trigger: 'noConfig' as TriggerType, From 7e942c6d3e0d3ec64e47c636085594d6ca923851 Mon Sep 17 00:00:00 2001 From: Zentrik Date: Tue, 6 Jan 2026 23:21:09 +0000 Subject: [PATCH 2/8] [AI] Update tests --- src/test/unittest/noConfigDebugInit.unit.test.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/test/unittest/noConfigDebugInit.unit.test.ts b/src/test/unittest/noConfigDebugInit.unit.test.ts index 3cf6e401..6374dbac 100644 --- a/src/test/unittest/noConfigDebugInit.unit.test.ts +++ b/src/test/unittest/noConfigDebugInit.unit.test.ts @@ -19,7 +19,7 @@ suite('setup for no-config debug scenario', function () { let context: TypeMoq.IMock; let noConfigScriptsDir: string; let bundledDebugPath: string; - let DEBUGPY_ADAPTER_ENDPOINTS = 'DEBUGPY_ADAPTER_ENDPOINTS'; + let DEBUGPY_ADAPTER_ENDPOINTS = 'VSCODE_DEBUGPY_ADAPTER_ENDPOINTS'; let BUNDLED_DEBUGPY_PATH = 'BUNDLED_DEBUGPY_PATH'; let workspaceUriStub: sinon.SinonStub; @@ -193,10 +193,12 @@ suite('setup for no-config debug scenario', function () { // Assert sinon.assert.calledOnce(createFileSystemWatcherFunct); - const expectedPattern = new RelativePattern( - path.join(os.tmpdir(), '.noConfigDebugAdapterEndpoints'), - '**/*.txt', - ); + const expectedPattern = sinon + .match.instanceOf(RelativePattern) + .and( + sinon.match.has('base', path.join(os.tmpdir(), '.noConfigDebugAdapterEndpoints')), + ) + .and(sinon.match.has('pattern', sinon.match(/^endpoint-[0-9a-f]{16}\.txt$/))); sinon.assert.calledWith(createFileSystemWatcherFunct, expectedPattern); }); From eec9bbe0041098c33287324791599a7f3c0d9941 Mon Sep 17 00:00:00 2001 From: Zentrik Date: Tue, 6 Jan 2026 23:21:53 +0000 Subject: [PATCH 3/8] Fix typo --- src/test/unittest/noConfigDebugInit.unit.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/unittest/noConfigDebugInit.unit.test.ts b/src/test/unittest/noConfigDebugInit.unit.test.ts index 6374dbac..706d9c98 100644 --- a/src/test/unittest/noConfigDebugInit.unit.test.ts +++ b/src/test/unittest/noConfigDebugInit.unit.test.ts @@ -49,7 +49,7 @@ suite('setup for no-config debug scenario', function () { workspaceUriStub.restore(); }); - test('should add environment variables for DEBUGPY_ADAPTER_ENDPOINTS, BUNDLED_DEBUGPY_PATH, and PATH', async () => { + test('should add environment variables for VSCODE_DEBUGPY_ADAPTER_ENDPOINTS, BUNDLED_DEBUGPY_PATH, and PATH', async () => { const environmentVariableCollectionMock = TypeMoq.Mock.ofType(); envVarCollectionReplaceStub = sinon.stub(); envVarCollectionAppendStub = sinon.stub(); From b4a023b2fd05367fbbff0ce1abcd94e5234450b3 Mon Sep 17 00:00:00 2001 From: Zentrik Date: Tue, 6 Jan 2026 23:22:21 +0000 Subject: [PATCH 4/8] Remove driveby fix --- src/test/unittest/noConfigDebugInit.unit.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/unittest/noConfigDebugInit.unit.test.ts b/src/test/unittest/noConfigDebugInit.unit.test.ts index 706d9c98..4b5817a1 100644 --- a/src/test/unittest/noConfigDebugInit.unit.test.ts +++ b/src/test/unittest/noConfigDebugInit.unit.test.ts @@ -19,7 +19,7 @@ suite('setup for no-config debug scenario', function () { let context: TypeMoq.IMock; let noConfigScriptsDir: string; let bundledDebugPath: string; - let DEBUGPY_ADAPTER_ENDPOINTS = 'VSCODE_DEBUGPY_ADAPTER_ENDPOINTS'; + let DEBUGPY_ADAPTER_ENDPOINTS = 'DEBUGPY_ADAPTER_ENDPOINTS'; let BUNDLED_DEBUGPY_PATH = 'BUNDLED_DEBUGPY_PATH'; let workspaceUriStub: sinon.SinonStub; @@ -49,7 +49,7 @@ suite('setup for no-config debug scenario', function () { workspaceUriStub.restore(); }); - test('should add environment variables for VSCODE_DEBUGPY_ADAPTER_ENDPOINTS, BUNDLED_DEBUGPY_PATH, and PATH', async () => { + test('should add environment variables for DEBUGPY_ADAPTER_ENDPOINTS, BUNDLED_DEBUGPY_PATH, and PATH', async () => { const environmentVariableCollectionMock = TypeMoq.Mock.ofType(); envVarCollectionReplaceStub = sinon.stub(); envVarCollectionAppendStub = sinon.stub(); From ac45ac4ed2f27f44fb8e562ca9acb9bbe60393eb Mon Sep 17 00:00:00 2001 From: Zentrik Date: Thu, 8 Jan 2026 23:21:10 +0000 Subject: [PATCH 5/8] Format --- src/test/unittest/noConfigDebugInit.unit.test.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/test/unittest/noConfigDebugInit.unit.test.ts b/src/test/unittest/noConfigDebugInit.unit.test.ts index 4b5817a1..206eb1d1 100644 --- a/src/test/unittest/noConfigDebugInit.unit.test.ts +++ b/src/test/unittest/noConfigDebugInit.unit.test.ts @@ -193,11 +193,9 @@ suite('setup for no-config debug scenario', function () { // Assert sinon.assert.calledOnce(createFileSystemWatcherFunct); - const expectedPattern = sinon - .match.instanceOf(RelativePattern) - .and( - sinon.match.has('base', path.join(os.tmpdir(), '.noConfigDebugAdapterEndpoints')), - ) + const expectedPattern = sinon.match + .instanceOf(RelativePattern) + .and(sinon.match.has('base', path.join(os.tmpdir(), '.noConfigDebugAdapterEndpoints'))) .and(sinon.match.has('pattern', sinon.match(/^endpoint-[0-9a-f]{16}\.txt$/))); sinon.assert.calledWith(createFileSystemWatcherFunct, expectedPattern); }); From c997d6999137035065f3fc25219ce4bdcb37997f Mon Sep 17 00:00:00 2001 From: Zentrik Date: Thu, 8 Jan 2026 23:46:15 +0000 Subject: [PATCH 6/8] Cleanup test and try to fix it failing on windows I guess this is what I get for trusting AI at midnight. --- src/test/unittest/noConfigDebugInit.unit.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/unittest/noConfigDebugInit.unit.test.ts b/src/test/unittest/noConfigDebugInit.unit.test.ts index 206eb1d1..79077053 100644 --- a/src/test/unittest/noConfigDebugInit.unit.test.ts +++ b/src/test/unittest/noConfigDebugInit.unit.test.ts @@ -19,6 +19,7 @@ suite('setup for no-config debug scenario', function () { let context: TypeMoq.IMock; let noConfigScriptsDir: string; let bundledDebugPath: string; + let noConfigEndpointDir: string; let DEBUGPY_ADAPTER_ENDPOINTS = 'DEBUGPY_ADAPTER_ENDPOINTS'; let BUNDLED_DEBUGPY_PATH = 'BUNDLED_DEBUGPY_PATH'; let workspaceUriStub: sinon.SinonStub; @@ -33,6 +34,7 @@ suite('setup for no-config debug scenario', function () { context.setup((c) => c.subscriptions).returns(() => []); noConfigScriptsDir = path.join(context.object.extensionPath, 'bundled/scripts/noConfigScripts'); bundledDebugPath = path.join(context.object.extensionPath, 'bundled/libs/debugpy'); + noConfigEndpointDir = path.join(context.object.extensionPath, '.noConfigDebugAdapterEndpoints'); // Stub crypto.randomBytes with proper typing let randomBytesStub = sinon.stub(crypto, 'randomBytes'); @@ -195,7 +197,7 @@ suite('setup for no-config debug scenario', function () { sinon.assert.calledOnce(createFileSystemWatcherFunct); const expectedPattern = sinon.match .instanceOf(RelativePattern) - .and(sinon.match.has('base', path.join(os.tmpdir(), '.noConfigDebugAdapterEndpoints'))) + .and(sinon.match.has('base', noConfigEndpointDir)) .and(sinon.match.has('pattern', sinon.match(/^endpoint-[0-9a-f]{16}\.txt$/))); sinon.assert.calledWith(createFileSystemWatcherFunct, expectedPattern); }); From bedc7d25e6467fb23b8664c57de0e10d1adef1fc Mon Sep 17 00:00:00 2001 From: Zentrik Date: Fri, 9 Jan 2026 00:19:06 +0000 Subject: [PATCH 7/8] Fix failing test on windows take 2 The error seems to be that `base` is `c:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\.noConfigDebugAdapterEndpoints` but `noConfigEndpointDir` is `C:\Users\RUNNER~1\AppData\Local\Temp\.noConfigDebugAdapterEndpoints` (the C is capitalised). Getting the fsPath of the URI should normalise the path the same as `base`. --- src/test/unittest/noConfigDebugInit.unit.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/unittest/noConfigDebugInit.unit.test.ts b/src/test/unittest/noConfigDebugInit.unit.test.ts index 79077053..b24d178b 100644 --- a/src/test/unittest/noConfigDebugInit.unit.test.ts +++ b/src/test/unittest/noConfigDebugInit.unit.test.ts @@ -197,7 +197,7 @@ suite('setup for no-config debug scenario', function () { sinon.assert.calledOnce(createFileSystemWatcherFunct); const expectedPattern = sinon.match .instanceOf(RelativePattern) - .and(sinon.match.has('base', noConfigEndpointDir)) + .and(sinon.match.has('base', Uri.file(noConfigEndpointDir).fsPath)) .and(sinon.match.has('pattern', sinon.match(/^endpoint-[0-9a-f]{16}\.txt$/))); sinon.assert.calledWith(createFileSystemWatcherFunct, expectedPattern); }); From 7f485551a2b5c4778169958b698db761a870c5c6 Mon Sep 17 00:00:00 2001 From: Zentrik Date: Sat, 10 Jan 2026 19:44:08 +0000 Subject: [PATCH 8/8] Remove workspace from hash --- src/extension/noConfigDebugInit.ts | 21 ++++--------------- .../unittest/noConfigDebugInit.unit.test.ts | 19 ++++++++--------- 2 files changed, 13 insertions(+), 27 deletions(-) diff --git a/src/extension/noConfigDebugInit.ts b/src/extension/noConfigDebugInit.ts index bb5e3f62..a83d000e 100644 --- a/src/extension/noConfigDebugInit.ts +++ b/src/extension/noConfigDebugInit.ts @@ -11,7 +11,6 @@ import { env, l10n, RelativePattern, - workspace, } from 'vscode'; import { createFileSystemWatcher, debugStartDebugging } from './utils'; import { traceError, traceVerbose } from './common/log/logging'; @@ -40,24 +39,12 @@ export async function registerNoConfigDebug( const collection = envVarCollection; // create a temp directory for the noConfigDebugAdapterEndpoints - // file path format: extPath/.noConfigDebugAdapterEndpoints/endpoint-stableWorkspaceHash.txt - let workspaceString = workspace.workspaceFile?.fsPath; - if (!workspaceString) { - workspaceString = workspace.workspaceFolders?.map((e) => e.uri.fsPath).join(';'); - } - if (!workspaceString) { - traceError('No workspace folder found'); - return Promise.resolve(new Disposable(() => {})); - } - - // Create unique hash based on workspace and VS Code session ID - const hash = crypto.createHash('sha256'); - hash.update(workspaceString.toString()); - hash.update(env.sessionId); - const stableWorkspaceHash = hash.digest('hex').slice(0, 16); + // file path format: extPath/.noConfigDebugAdapterEndpoints/endpoint-.txt + // sessionId is unique per VS Code window, ensuring isolation between windows const tempDirPath = path.join(extPath, '.noConfigDebugAdapterEndpoints'); - const endpointFilename = `endpoint-${stableWorkspaceHash}.txt`; + const sessionIdHash = crypto.createHash('sha256').update(env.sessionId).digest('hex').slice(0, 16); + const endpointFilename = `endpoint-${sessionIdHash}.txt`; const tempFilePath = path.join(tempDirPath, endpointFilename); // create the temp directory if it doesn't exist diff --git a/src/test/unittest/noConfigDebugInit.unit.test.ts b/src/test/unittest/noConfigDebugInit.unit.test.ts index b24d178b..f7261807 100644 --- a/src/test/unittest/noConfigDebugInit.unit.test.ts +++ b/src/test/unittest/noConfigDebugInit.unit.test.ts @@ -6,7 +6,7 @@ import { IExtensionContext } from '../../extension/common/types'; import { registerNoConfigDebug as registerNoConfigDebug } from '../../extension/noConfigDebugInit'; import * as TypeMoq from 'typemoq'; import * as sinon from 'sinon'; -import { DebugConfiguration, DebugSessionOptions, RelativePattern, Uri, workspace } from 'vscode'; +import { DebugConfiguration, DebugSessionOptions, env, RelativePattern, Uri } from 'vscode'; import * as utils from '../../extension/utils'; import { assert } from 'console'; import * as fs from 'fs'; @@ -19,10 +19,10 @@ suite('setup for no-config debug scenario', function () { let context: TypeMoq.IMock; let noConfigScriptsDir: string; let bundledDebugPath: string; - let noConfigEndpointDir: string; let DEBUGPY_ADAPTER_ENDPOINTS = 'DEBUGPY_ADAPTER_ENDPOINTS'; let BUNDLED_DEBUGPY_PATH = 'BUNDLED_DEBUGPY_PATH'; - let workspaceUriStub: sinon.SinonStub; + const testSessionId = 'test-session-id-1234'; + const hashedSessionId = crypto.createHash('sha256').update(testSessionId).digest('hex').slice(0, 16); const testDataDir = path.join(__dirname, 'testData'); const testFilePath = path.join(testDataDir, 'debuggerAdapterEndpoint.txt'); @@ -34,21 +34,20 @@ suite('setup for no-config debug scenario', function () { context.setup((c) => c.subscriptions).returns(() => []); noConfigScriptsDir = path.join(context.object.extensionPath, 'bundled/scripts/noConfigScripts'); bundledDebugPath = path.join(context.object.extensionPath, 'bundled/libs/debugpy'); - noConfigEndpointDir = path.join(context.object.extensionPath, '.noConfigDebugAdapterEndpoints'); // Stub crypto.randomBytes with proper typing let randomBytesStub = sinon.stub(crypto, 'randomBytes'); // Provide a valid Buffer object randomBytesStub.callsFake((_size: number) => Buffer.from('1234567899', 'hex')); - workspaceUriStub = sinon.stub(workspace, 'workspaceFolders').value([{ uri: Uri.parse(os.tmpdir()) }]); + // Stub env.sessionId to return a consistent value for tests + sinon.stub(env, 'sessionId').value(testSessionId); } catch (error) { console.error('Error in setup:', error); } }); teardown(() => { sinon.restore(); - workspaceUriStub.restore(); }); test('should add environment variables for DEBUGPY_ADAPTER_ENDPOINTS, BUNDLED_DEBUGPY_PATH, and PATH', async () => { @@ -195,10 +194,10 @@ suite('setup for no-config debug scenario', function () { // Assert sinon.assert.calledOnce(createFileSystemWatcherFunct); - const expectedPattern = sinon.match - .instanceOf(RelativePattern) - .and(sinon.match.has('base', Uri.file(noConfigEndpointDir).fsPath)) - .and(sinon.match.has('pattern', sinon.match(/^endpoint-[0-9a-f]{16}\.txt$/))); + const expectedPattern = new RelativePattern( + path.join(os.tmpdir(), '.noConfigDebugAdapterEndpoints'), + `endpoint-${hashedSessionId}.txt`, + ); sinon.assert.calledWith(createFileSystemWatcherFunct, expectedPattern); });