Skip to content

workspace: add suppressConfirmation option to updateWorkspaceFolders#306519

Open
RockyWearsAHat wants to merge 7 commits intomicrosoft:mainfrom
RockyWearsAHat:feature/suppress-workspace-folder-confirmation
Open

workspace: add suppressConfirmation option to updateWorkspaceFolders#306519
RockyWearsAHat wants to merge 7 commits intomicrosoft:mainfrom
RockyWearsAHat:feature/suppress-workspace-folder-confirmation

Conversation

@RockyWearsAHat
Copy link
Copy Markdown

@RockyWearsAHat RockyWearsAHat commented Mar 30, 2026

Summary

Add a suppressConfirmation option to updateWorkspaceFolders() that allows extensions to skip the workspace-transition confirmation dialog when programmatically modifying workspace folders.

Closes #306495

Problem

When an extension calls vscode.workspace.updateWorkspaceFolders() and the change triggers a workspace transition (e.g., single-folder to multi-folder, or replacing all folders), VS Code shows a confirmation dialog warning about extension host restarts. Extensions cannot suppress this dialog, even when they fully understand and expect the lifecycle implications.

This blocks several automated workflows:

  • Project scaffolding tools that set up multi-folder workspaces
  • Branch-per-chat session managers that swap workspace folders programmatically
  • Workspace migration utilities
  • Testing frameworks that need deterministic workspace setup

In all these cases, the confirmation dialog interrupts a programmatic flow where the extension has already committed to the operation.

Approach

New API Surface

// New options interface
interface UpdateWorkspaceFoldersOptions {
  readonly suppressConfirmation?: boolean;
}

// New overload (existing overload is unchanged)
workspace.updateWorkspaceFolders(
  0,
  1,
  { suppressConfirmation: true },
  { uri: newUri },
);

Implementation Architecture

The option flows through the full call chain:

vscode.d.ts (UpdateWorkspaceFoldersOptions + overload)
    → extHost.api.impl.ts (overload detection: options bag vs folder object)
    → extHostWorkspace.ts (forwards suppressConfirmation via IPC)
    → extHost.protocol.ts (flat boolean across IPC boundary)
    → mainThreadWorkspace.ts (constructs IEnterWorkspaceOptions)
    → IWorkspaceEditingService (new IEnterWorkspaceOptions interface)
    → abstractWorkspaceEditingService.ts (threads through updateFolders)
    → workspaceEditingService.ts (passes force to stopExtensionHosts)
    → IExtensionService.stopExtensionHosts (new force parameter)
    → abstractExtensionService.ts (bypasses veto chain when force=true)

Key design decisions:

  1. Named interface, not inline type: IEnterWorkspaceOptions is defined once in workspaceEditing.ts and imported everywhere, following VS Code's convention for options that flow through service layers.

  2. force parameter on stopExtensionHosts: Rather than having the workspace service call the protected _doStopExtensionHosts() directly (breaking encapsulation), this PR adds a force?: boolean parameter to the public stopExtensionHosts(reason, auto, force?) method. When force is true, the method bypasses the extension host veto chain and calls _doStopExtensionHosts() internally. This keeps the extension service's internal state management intact.

  3. Flat boolean across IPC: The protocol uses a flat suppressConfirmation?: boolean rather than a nested options object, following the existing IPC convention for optional parameters.

  4. Overload detection: The extHost.api.impl.ts binding distinguishes between updateWorkspaceFolders(0, 1, { uri }) (folder) and updateWorkspaceFolders(0, 1, { suppressConfirmation: true }) (options) by checking for the uri property. This is safe because uri is a required property on workspace folder descriptors.

Files Changed (11)

File Change
vscode.d.ts UpdateWorkspaceFoldersOptions interface + overload
extHost.api.impl.ts Overload detection and forwarding
extHostWorkspace.ts Accept + forward suppressConfirmation
extHost.protocol.ts IPC parameter
mainThreadWorkspace.ts Construct IEnterWorkspaceOptions
workspaceEditing.ts IEnterWorkspaceOptions interface definition
abstractWorkspaceEditingService.ts Thread options through updateFolders
workspaceEditingService.ts (electron) Pass force to stopExtensionHosts
workspaceEditingService.ts (browser) Interface conformance
extensions.ts force param on stopExtensionHosts
abstractExtensionService.ts Bypass veto chain when force=true

Use Cases

  • Branch-per-chat workflows: An extension creates isolated worktrees per chat session and swaps workspace folders when the user switches chats. The confirmation dialog would fire on every switch, making the experience unusable.
  • Project scaffolding: git copilot-quickstart and similar tools set up multi-folder workspaces as part of project initialization. The dialog interrupts an automated flow where the tool knows exactly what it's doing.
  • Workspace migration: Tools that convert single-folder workspaces to multi-root workspaces need the transition to be seamless.
  • Test harnesses: Integration tests that set up workspace state need deterministic, non-interactive execution.

Testing

Manual verification that:

  1. Calling updateWorkspaceFolders without options shows the dialog as before (backward compatible)
  2. Calling with { suppressConfirmation: true } skips the dialog and proceeds immediately
  3. The overload correctly distinguishes { uri } (folder) from { suppressConfirmation } (options)
  4. Extension host restart proceeds normally when the dialog is suppressed

Add an UpdateWorkspaceFoldersOptions interface with a suppressConfirmation
flag that allows extensions to skip the workspace-transition confirmation
dialog when programmatically updating workspace folders.

The change threads the option through the full call chain:
- vscode.d.ts: new overload and UpdateWorkspaceFoldersOptions interface
- extHost.api.impl.ts: overload detection (options bag vs folder object)
- extHostWorkspace.ts: forwards suppressConfirmation via IPC
- extHost.protocol.ts: flat boolean across the IPC boundary
- mainThreadWorkspace.ts: constructs IEnterWorkspaceOptions for the service
- IWorkspaceEditingService: new IEnterWorkspaceOptions interface
- abstractWorkspaceEditingService.ts: threads options through updateFolders
- electron-browser workspaceEditingService.ts: passes force flag to
  stopExtensionHosts when suppressConfirmation is true
- IExtensionService.stopExtensionHosts: new force parameter that bypasses
  the extension host veto chain

Ref: microsoft#306495
@vs-code-engineering
Copy link
Copy Markdown
Contributor

vs-code-engineering Bot commented Mar 30, 2026

📬 CODENOTIFY

The following users are being notified based on files changed in this PR:

@bpasero

Matched files:

  • src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts
  • src/vs/workbench/services/workspaces/browser/workspaceEditingService.ts
  • src/vs/workbench/services/workspaces/common/workspaceEditing.ts
  • src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new suppressConfirmation option to vscode.workspace.updateWorkspaceFolders() so extensions can programmatically trigger workspace-folder transitions without being blocked by the extension-host-restart confirmation dialog, threading the option through the ext host → main thread → workspace editing → extension service stop flow.

Changes:

  • Introduces UpdateWorkspaceFoldersOptions + a new updateWorkspaceFolders overload in vscode.d.ts.
  • Propagates suppressConfirmation across the ext host IPC boundary and into workspace editing services via a new IEnterWorkspaceOptions.
  • Extends IExtensionService.stopExtensionHosts with a force parameter intended to bypass the confirmation/veto flow.

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/vscode-dts/vscode.d.ts Adds UpdateWorkspaceFoldersOptions and a new overload to surface suppressConfirmation to extensions.
src/vs/workbench/api/common/extHost.api.impl.ts Implements overload detection and forwards suppressConfirmation into extHost workspace logic.
src/vs/workbench/api/common/extHostWorkspace.ts Threads suppressConfirmation to the main thread IPC call for workspace folder updates.
src/vs/workbench/api/common/extHost.protocol.ts Extends the main-thread workspace RPC signature with suppressConfirmation?: boolean.
src/vs/workbench/api/browser/mainThreadWorkspace.ts Receives suppressConfirmation and forwards it into IWorkspaceEditingService.updateFolders as options.
src/vs/workbench/services/workspaces/common/workspaceEditing.ts Defines IEnterWorkspaceOptions and adds optional options params to workspace-editing service APIs.
src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts Adds options plumbing in updateFolders and forwards options through createAndEnterWorkspace/enterWorkspace signatures.
src/vs/workbench/services/workspaces/browser/workspaceEditingService.ts Updates browser implementation signature to conform to new enterWorkspace(..., options?).
src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts Uses options?.suppressConfirmation to invoke extension host stopping with force.
src/vs/workbench/services/extensions/common/extensions.ts Extends IExtensionService.stopExtensionHosts API with a force?: boolean parameter.
src/vs/workbench/services/extensions/common/abstractExtensionService.ts Implements force behavior in stopExtensionHosts by bypassing the veto path.

Comment thread src/vs/workbench/services/extensions/common/abstractExtensionService.ts Outdated
Comment thread src/vs/workbench/services/workspaces/common/workspaceEditing.ts Outdated
Address review feedback:
- stopExtensionHosts now forwards force to _doStopExtensionHostsWithVeto
  instead of bypassing onWillStop entirely, so state persistence hooks run
- After awaiting vetos, force mode stops unconditionally (ignoring results)
- Thread options through doAddFolders and removeFolders so
  suppressConfirmation works on all updateFolders code paths
- Update JSDoc to reflect that onWillStop listeners still fire
@RockyWearsAHat RockyWearsAHat force-pushed the feature/suppress-workspace-folder-confirmation branch from 34c47fe to 72a11c5 Compare March 30, 2026 23:19
@RockyWearsAHat RockyWearsAHat requested a review from Copilot March 30, 2026 23:31
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 11 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/vs/workbench/api/common/extHostWorkspace.ts:289

  • Adding suppressConfirmation?: boolean as a positional parameter before the rest arg changes the calling convention: any existing internal caller that does updateWorkspaceFolders(ext, 0, 0, folder) will now treat folder as suppressConfirmation and add zero folders. Please update all direct call sites (notably src/vs/workbench/api/test/browser/extHostWorkspace.test.ts) to pass undefined for the new parameter, or refactor to avoid a positional insert that can be miscalled.
	updateWorkspaceFolders(extension: IExtensionDescription, index: number, deleteCount: number, suppressConfirmation?: boolean, ...workspaceFoldersToAdd: { uri: vscode.Uri; name?: string }[]): boolean {
		const validatedDistinctWorkspaceFoldersToAdd: { uri: vscode.Uri; name?: string }[] = [];
		if (Array.isArray(workspaceFoldersToAdd)) {
			workspaceFoldersToAdd.forEach(folderToAdd => {
				if (URI.isUri(folderToAdd.uri) && !validatedDistinctWorkspaceFoldersToAdd.some(f => isFolderEqual(f.uri, folderToAdd.uri, this._extHostFileSystemInfo))) {

Comment thread src/vs/workbench/api/common/extHost.api.impl.ts
- extensionService.test.ts: verify stopExtensionHosts with force flag
  bypasses sync and async vetos, disposes hosts, and fires onWillStop
- extHostWorkspace.test.ts: verify suppressConfirmation parameter is
  forwarded through the proxy to $updateWorkspaceFolders
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/vs/workbench/api/common/extHostWorkspace.ts:289

  • Changing updateWorkspaceFolders to take suppressConfirmation as the 4th parameter breaks existing internal call sites that pass folders as the 4th argument (e.g. existing tests and any other direct callers): the first folder object will be treated as suppressConfirmation, resulting in no folders being added and the call returning false (no-op). To avoid this regression, add runtime overload handling (e.g. if the 4th arg is not a boolean, treat it as the first folder to add) and/or update all direct callers accordingly.
	updateWorkspaceFolders(extension: IExtensionDescription, index: number, deleteCount: number, suppressConfirmation?: boolean, ...workspaceFoldersToAdd: { uri: vscode.Uri; name?: string }[]): boolean {
		const validatedDistinctWorkspaceFoldersToAdd: { uri: vscode.Uri; name?: string }[] = [];
		if (Array.isArray(workspaceFoldersToAdd)) {
			workspaceFoldersToAdd.forEach(folderToAdd => {
				if (URI.isUri(folderToAdd.uri) && !validatedDistinctWorkspaceFoldersToAdd.some(f => isFolderEqual(f.uri, folderToAdd.uri, this._extHostFileSystemInfo))) {

Comment thread src/vs/workbench/services/extensions/common/extensions.ts
Match the updated IExtensionService interface that now accepts
(reason, auto?, force?) parameters.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 2 comments.

Comment thread src/vs/workbench/api/common/extHost.api.impl.ts Outdated
Comment thread src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 1 comment.

Comment thread src/vs/workbench/services/extensions/common/abstractExtensionService.ts Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated no new comments.

When an extension calls updateWorkspaceFolders with
suppressConfirmation: true for the first time, a one-time consent
dialog asks the user to approve. The decision is persisted
per-extension in application-scoped storage.

This prevents any extension from silently redirecting the user's
workspace without explicit consent, addressing the security concern
that the raw suppressConfirmation bypass could be abused by
malicious extensions.

Changes:
- Thread extensionId through the IPC boundary alongside extensionName
- Inject IStorageService + IDialogService into MainThreadWorkspace
- Add _resolveSuppress() trust gate with confirm dialog
- Update vscode.d.ts docs to describe first-use consent behavior
- Update test to verify extensionId is forwarded
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 3 comments.

Comment on lines 792 to +798
const veto = await handleVetos(vetos, error => this._logService.error(error));
if (force) {
// force mode: onWillStop fired above so listeners could persist state,
// but we ignore veto results and stop unconditionally.
await this._doStopExtensionHosts();
return true;
}
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In force mode, this still uses handleVetos(vetos, ...), which short-circuits as soon as any listener calls veto(true, ...) synchronously. That means other listeners’ long-running veto promises may not be awaited before extension hosts are stopped, despite the API/docs implying listeners can run long operations before stop. Consider a dedicated force path that fires onWillStop and then awaits settlement of all thenable veto values (logging errors) without short-circuiting, before stopping hosts unconditionally.

Suggested change
const veto = await handleVetos(vetos, error => this._logService.error(error));
if (force) {
// force mode: onWillStop fired above so listeners could persist state,
// but we ignore veto results and stop unconditionally.
await this._doStopExtensionHosts();
return true;
}
if (force) {
// Force mode: onWillStop fired above so listeners can persist state.
// We ignore veto results but still await settlement of all veto promises
// (without short-circuiting) before stopping the extension hosts.
const vetoPromises: Promise<unknown>[] = [];
for (const value of vetos) {
if (typeof value === 'boolean') {
continue;
}
vetoPromises.push(value.then(undefined, error => this._logService.error(error)));
}
if (vetoPromises.length > 0) {
await Promise.allSettled(vetoPromises);
}
await this._doStopExtensionHosts();
return true;
}
const veto = await handleVetos(vetos, error => this._logService.error(error));

Copilot uses AI. Check for mistakes.
Comment on lines +125 to +131
detail: localize(
'suppressConfirmation.detail',
"This extension is requesting permission to add, remove, or replace workspace folders without showing the confirmation dialog. This enables automated workflows such as branch-per-chat session switching.\n\nYou can change this later in Settings."
),
primaryButton: localize('suppressConfirmation.allow', "Allow"),
cancelButton: localize('suppressConfirmation.deny', "Don't Allow"),
});
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The consent dialog text says "You can change this later in Settings.", but this PR only persists a boolean in application storage and there doesn’t appear to be any Settings surface to manage/revoke this permission. Please either implement a discoverable way to change it (e.g. a setting/command/permissions UI) or adjust the dialog copy to avoid implying a settings toggle exists.

Copilot uses AI. Check for mistakes.
Comment on lines +122 to +123
"'{0}' wants to modify workspace folders silently",
extensionName
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a permission/consent prompt, relying only on extensionName (displayName/name) is spoofable/ambiguous. Since you already receive extensionId, consider including it (or a publisher-qualified label) in the dialog message/detail so users can clearly identify which extension is requesting silent workspace changes.

Suggested change
"'{0}' wants to modify workspace folders silently",
extensionName
"'{0}' ({1}) wants to modify workspace folders silently",
extensionName,
extensionId

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add suppressConfirmation option to updateWorkspaceFolders()

3 participants