microsoft / microsoft/vscode

`executeTask()` activates *all* extensions with a task provider when the task came from `fetchTasks()`

Open
#336,152 0 comments 0 reactions 1 assignee Claimed by @meganrogge View on GitHub
Dominant language
TypeScript
Stars
193k
Forks
42.4k
PR merge metrics
PR metrics pending

Description

Type: Bug

VS Code version: Code - Insiders 1.138.0-insider (b065ad9cd83dae266607baf77f10c1fbf0330fba, 2026-09-11T17:49:28-07:00)
OS version: Windows_NT x64 10.0.22631
Modes:
Extensions: none

### Summary

Calling `vscode.tasks.executeTask(task)` with a `Task` object obtained from `vscode.tasks.fetchTasks({ type: 'mytasktype' })` activates **every installed extension that contributes a `taskDefinition`**, not just the providers for the task's own type.

Executing an equivalent `Task` that the extension constructed itself activates only providers of the matching type, as expected. So the unwanted activation is triggered purely by round-tripping the task through `fetchTasks()`.

Note that `fetchTasks({ type })` itself behaves correctly — it only activates providers of the requested type. It is the subsequent `executeTask()` that fans out.

### Steps to Reproduce

Create a minimal reproducer extension:

```jsonc
// package.json
"activationEvents": ["onStartupFinished"],
"contributes": {
"taskDefinitions": [{ "type": "mytasktype" }]
}
```

```ts
// extension.ts
const TASK_TYPE = 'mytasktype';
const TASK = new vscode.Task(
{ type: TASK_TYPE },
vscode.TaskScope.Workspace,
'hello',
TASK_TYPE,
new vscode.ShellExecution('echo hello')
);

export async function activate(context: vscode.ExtensionContext): Promise {
context.subscriptions.push(
vscode.tasks.registerTaskProvider(TASK_TYPE, {
provideTasks() { return [TASK]; },
resolveTask() { return undefined; }
})
);

const tasks = await vscode.tasks.fetchTasks({ type: TASK_TYPE });

// await vscode.tasks.executeTask(TASK); // OK: activates only "mytasktype" providers
await vscode.tasks.executeTask(tasks[0]); // BUG: activates *all* task providers
}
```

1. Launch the reproducer extension in an Extension Development Host on an otherwise empty workspace.
2. Observe in the *Tasks* output channel, that after activating task providers for type mytasktype, all other providers are activated as well (twice!).
Image

3. You may also observe it in the *Running Extensions* view, where you'll see that the built-in *Gulp* extension is activated which is not usually the case in an empty workspace.
4. Swap the two `executeTask` lines and repeat: Only `mytasktype` providers activate.
### Expected

`executeTask()` activates only the task providers needed to resolve and run the given task regardless of whether the `Task` object came from `fetchTasks()` or was constructed by the caller.

### Possible precedent

A similar issue was described in #175821 and fixed in #178679.

### Impact

Extensions that run tasks programmatically cause unrelated extensions to activate in workspaces where they have nothing to do. `executeTask()` of a fetched task pays the cost of waking all task-provider extensions, and can block for up to the 5s `raceTimeout` in `_activateTaskProviders` ("Timed out activating extensions for task providers", cf. #181844).

-------

### Analysis (AI-generated!)

The two code paths in `ExtHostTask.executeTask()` (`src/vs/workbench/api/node/extHostTask.ts`) are not equivalent:

- A task created by the extension has no `_id`, so it is sent to the main thread as a full `TaskDTO`, and no lookup is needed.
- A task returned by `fetchTasks()` has a preserved `_id`, so it is sent as a `TaskHandleDTO`. `ITaskHandleDTO` (`src/vs/workbench/api/common/shared/tasks.ts`) only carries `{ id, workspaceFolder }` — **the task type is dropped at this point.**

On the main thread, both `$getTaskExecution()` and `$executeTask()` in `src/vs/workbench/api/browser/mainThreadTask.ts` then call:

```ts
this._taskService.getTask(workspace, value.id, true); // 4th arg `type` omitted
```

`AbstractTaskService.getTask()` does not find the task via `_findWorkspaceTasks()` (it is provider-contributed, not in `tasks.json`), so it falls through to:

```ts
const map = await this._getGroupedTasks({ type }); // type === undefined
```

which reaches `_activateTaskProviders(undefined)` → `_getActivationEvents(undefined)`:

```ts
} else {
// send activation events for all task types
for (const definition of TaskDefinitionRegistry.all()) {
result.push(`onTaskType:${definition.taskType}`);
}
}
```

…activating every extension that contributes a task definition.

This is the same failure mode as #175821, which was fixed in PR #178679 by adding the `type` parameter to `getTask()` and passing it from `AbstractTaskService._executeTask()`. The extension-host entry points were not updated, so they still call `getTask()` without a type and re-introduce the fan-out.

### Suggested fix (AI-generated!)

Thread the task type through the handle DTO, mirroring PR #178679:

1. Add an optional `type?: string` to `ITaskHandleDTO` in `src/vs/workbench/api/common/shared/tasks.ts`.
2. Populate it from `value.definition.type` in `TaskHandleDTO.from()` in `src/vs/workbench/api/common/extHostTask.ts`.
3. Pass it as the 4th argument in both `getTask()` calls in `mainThreadTask.ts` (`$getTaskExecution` and `$executeTask`).

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.