`executeTask()` activates *all* extensions with a task provider when the task came from `fetchTasks()`
- 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!).
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
Assessment
This issue has not been assessed yet.