Change:
Clone and npm install. Hit F5, it never starts: <img width="484" alt="image" src=""I am on macOS.
This is probably because a prelaunch task is still running / your problem matchers haven't matched anything. Please make sure you have the right problem matchers installed, otherwise I suggest opening issues for them if they don't match correctly.
Would be great if debug could give me a hint.
Will keep open to track it, it's not a rare problem
We should also keep the terminal open if the task fails, currently it closes immediately which can be confusing
Would also be nice if we could cancel the pending launch attempt, e.g. by showing the debug toolbar immediately and disabling all buttons except the stop button. Right now when I get into this state I end up just reloading the whole window and incurring the pain of all extensions reactivating, which takes several seconds on my machine (git, TS etc)
After 10 seconds we'll show !
<!-- UNABLETOLOCATECOMMITMESSAGE repo --Issue marked as unreleased but unable to locate closing commit in repo history. If this was closed in a separate repo you can add the label directly, or comment .
<!-- UNABLETOLOCATECOMMITMESSAGE --Issue marked as unreleased but unable to locate closing commit in issue timeline. You can manually reference a commit by commenting , or directly add the label if you know this has already been releaased

Discussion:
*  Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ <del> import * as nls from 'vs/nls'; </del> <ins> import { Action } from 'vs/base/common/actions'; import { disposableTimeout } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { createErrorWithActions } from 'vs/base/common/errorMessage'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; </ins> import severity from 'vs/base/common/severity'; <del> import { Event } from 'vs/base/common/event'; import { Markers } from 'vs/workbench/contrib/markers/common/markers'; import { ITaskService, ITaskSummary } from 'vs/workbench/contrib/tasks/common/taskService'; </del> <ins> import * as nls from 'vs/nls'; import { ICommandService } from 'vs/platform/commands/common/commands'; </ins> import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; <del> import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { ITaskEvent, TaskEventKind, ITaskIdentifier, Task } from 'vs/workbench/contrib/tasks/common/tasks'; </del> import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; <del> import { IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; </del> <ins> import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; </ins> import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; <del> import { createErrorWithActions } from 'vs/base/common/errorMessage'; import { Action } from 'vs/base/common/actions'; </del> <ins> import { IWorkspace, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; </ins> import { DEBUG_CONFIGURE_COMMAND_ID, DEBUG_CONFIGURE_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands'; <del> import { ICommandService } from 'vs/platform/commands/common/commands'; </del> <ins> import { IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug'; import { Markers } from 'vs/workbench/contrib/markers/common/markers'; import { ConfiguringTask, CustomTask, ITaskEvent, ITaskIdentifier, Task, TaskEventKind } from 'vs/workbench/contrib/tasks/common/tasks'; import { ITaskService, ITaskSummary } from 'vs/workbench/contrib/tasks/common/taskService'; import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; </ins> <del> function once(match: (e: ITaskEvent) => boolean, event: Event<ITaskEvent>): Event<ITaskEvent> { return (listener, thisArgs = null, disposables?) => { const result = event(e => { if (match(e)) { result.dispose(); return listener.call(thisArgs, e); } }, null, disposables); return result; }; } </del> <ins> const onceFilter = (event: Event<ITaskEvent>, filter: (e: ITaskEvent) => boolean) => Event.once(Event.filter(event, filter)); </ins> export const enum TaskRunResult { Failure,

} const DEBUG_TASK_ERROR_CHOICE_KEY = 'debug.taskerrorchoice'; <ins> const ABORT_LABEL = nls.localize('abort', "Abort"); const DEBUG_ANYWAY_LABEL = nls.localize({ key: 'debugAnyway', comment: ['&& denotes a mnemonic'] }, "&&Debug Anyway"); const DEBUG_ANYWAY_LABEL_NO_MEMO = nls.localize('debugAnywayNoMemo', "Debug Anyway"); </ins> <del> export class DebugTaskRunner { </del> <ins> interface IRunnerTaskSummary extends ITaskSummary { cancelled?: boolean; } export class DebugTaskRunner implements IDisposable { </ins> <del> private canceled = false; </del> <ins> private globalCancellation = new CancellationTokenSource(); </ins> constructor( @ITaskService private readonly taskService: ITaskService,

@IViewsService private readonly viewsService: IViewsService, @IDialogService private readonly dialogService: IDialogService, @IStorageService private readonly storageService: IStorageService, <del> @ICommandService private readonly commandService: ICommandService </del> <ins> @ICommandService private readonly commandService: ICommandService, @IProgressService private readonly progressService: IProgressService, </ins> ) { } cancel(): void { <del> this.canceled = true; </del> <ins> this.globalCancellation.dispose(true); this.globalCancellation = new CancellationTokenSource(); </ins> } <del> async runTaskAndCheckErrors(root: IWorkspaceFolder | IWorkspace | undefined, taskId: string | ITaskIdentifier | undefined): Promise<TaskRunResult> { </del> <ins> public dispose(): void { this.globalCancellation.dispose(true); } async runTaskAndCheckErrors( root: IWorkspaceFolder | IWorkspace | undefined, taskId: string | ITaskIdentifier | undefined, ): Promise<TaskRunResult> { </ins> try { <del> this.canceled = false; const taskSummary = await this.runTask(root, taskId); if (this.canceled || (taskSummary && taskSummary.exitCode === undefined)) { </del> <ins> const taskSummary = await this.runTask(root, taskId, this.globalCancellation.token); if (taskSummary && (taskSummary.exitCode === undefined || taskSummary.cancelled)) { </ins> // User canceled, either debugging, or the prelaunch task return TaskRunResult.Failure; }

message, buttons: [ { <del> label: nls.localize({ key: 'debugAnyway', comment: ['&& denotes a mnemonic'] }, "&&Debug Anyway"), </del> <ins> label: DEBUG_ANYWAY_LABEL, </ins> run: () => DebugChoice.DebugAnyway }, {

} ], cancelButton: { <del> label: nls.localize('abort', "Abort"), </del> <ins> label: ABORT_LABEL, </ins> run: () => DebugChoice.Cancel }, checkbox: {

} } <del> async runTask(root: IWorkspace | IWorkspaceFolder | undefined, taskId: string | ITaskIdentifier | undefined): Promise<ITaskSummary | null> { </del> <ins> async runTask(root: IWorkspace | IWorkspaceFolder | undefined, taskId: string | ITaskIdentifier | undefined, token = this.globalCancellation.token): Promise<IRunnerTaskSummary | null> { </ins> if (!taskId) { return Promise.resolve(null); }

// If a task is missing the problem matcher the promise will never complete, so we need to have a workaround #35340 let taskStarted = false; <ins> const store = new DisposableStore(); </ins> const getTaskKey = (t: Task) => t.getKey() ?? t.getMapKey(); const taskKey = getTaskKey(task); <del> const inactivePromise: Promise<ITaskSummary | null> = new Promise((c) => once(e => { // When a task isBackground it will go inactive when it is safe to launch. // But when a background task is terminated by the user, it will also fire an inactive event. // This means that we will not get to see the real exit code from running the task (undefined when terminated by the user). // Catch the ProcessEnded event here, which occurs before inactive, and capture the exit code to prevent this. return (e.kind === TaskEventKind.Inactive || (e.kind === TaskEventKind.ProcessEnded && e.exitCode === undefined)) && getTaskKey(e.__task) === taskKey; }, this.taskService.onDidStateChange)(e => { taskStarted = true; c(e.kind === TaskEventKind.ProcessEnded ? { exitCode: e.exitCode } : null); })); const promise: Promise<ITaskSummary | null> = this.taskService.getActiveTasks().then(async (tasks): Promise<ITaskSummary | null> => { </del> <ins> const inactivePromise: Promise<ITaskSummary | null> = new Promise((resolve) => store.add( onceFilter(this.taskService.onDidStateChange, e => { // When a task isBackground it will go inactive when it is safe to launch. // But when a background task is terminated by the user, it will also fire an inactive event. // This means that we will not get to see the real exit code from running the task (undefined when terminated by the user). // Catch the ProcessEnded event here, which occurs before inactive, and capture the exit code to prevent this. return (e.kind === TaskEventKind.Inactive || (e.kind === TaskEventKind.ProcessEnded && e.exitCode === undefined)) && getTaskKey(e.__task) === taskKey; })(e => { taskStarted = true; resolve(e.kind === TaskEventKind.ProcessEnded ? { exitCode: e.exitCode } : null); }), )); store.add( onceFilter(this.taskService.onDidStateChange, e => ((e.kind === TaskEventKind.Active) || (e.kind === TaskEventKind.DependsOnStarted)) && getTaskKey(e.__task) === taskKey )(() => { // Task is active, so everything seems to be fine, no need to prompt after 10 seconds // Use case being a slow running task should not be prompted even though it takes more than 10 seconds taskStarted = true; }) ); const didAcquireInput = store.add(new Emitter<void>()); store.add(onceFilter( this.taskService.onDidStateChange, e => (e.kind === TaskEventKind.AcquiredInput) && getTaskKey(e.__task) === taskKey )(() => didAcquireInput.fire())); const taskDonePromise: Promise<ITaskSummary | null> = this.taskService.getActiveTasks().then(async (tasks): Promise<ITaskSummary | null> => { </ins> if (tasks.find(t => getTaskKey(t) === taskKey)) { <ins> didAcquireInput.fire(); </ins> // Check that the task isn't busy and if it is, wait for it const busyTasks = await this.taskService.getBusyTasks(); if (busyTasks.find(t => getTaskKey(t) === taskKey)) {

// task is already running and isn't busy - nothing to do. return Promise.resolve(null); } <del> once(e => ((e.kind === TaskEventKind.Active) || (e.kind === TaskEventKind.DependsOnStarted)) && getTaskKey(e.__task) === taskKey, this.taskService.onDidStateChange)(() => { // Task is active, so everything seems to be fine, no need to prompt after 10 seconds // Use case being a slow running task should not be prompted even though it takes more than 10 seconds taskStarted = true; }); </del> const taskPromise = this.taskService.run(task); if (task.configurationProperties.isBackground) { return inactivePromise;

return taskPromise.then(x => x ?? null); }); <del> return new Promise((c, e) => { const waitForInput = new Promise<void>(resolve => once(e => (e.kind === TaskEventKind.AcquiredInput) && getTaskKey(e.__task) === taskKey, this.taskService.onDidStateChange)(() => { resolve(); })); promise.then(result => { </del> <ins> const result = new Promise<IRunnerTaskSummary | null>((resolve, reject) => { taskDonePromise.then(result => { </ins> taskStarted = true; <del> c(result); }, error => e(error)); </del> <ins> resolve(result); }, error => reject(error)); store.add(token.onCancellationRequested(() => { resolve({ exitCode: undefined, cancelled: true }); this.taskService.terminate(task).catch(() => { }); })); </ins> <del> waitForInput.then(() => { </del> <ins> // Start the timeouts once a terminal has been acquired store.add(didAcquireInput.event(() => { </ins> const waitTime = task.configurationProperties.isBackground ? 5000 : 10000; <del> setTimeout(() => { </del> <ins> // Error shown if there's a background task with no problem matcher that doesn't exit quickly store.add(disposableTimeout(() => { </ins> if (!taskStarted) { <del> const errorMessage = typeof taskId === 'string' ? nls.localize('taskNotTrackedWithTaskId', "The task '{0}' cannot be tracked. Make sure to have a problem matcher defined.", taskId) : nls.localize('taskNotTracked', "The task '{0}' cannot be tracked. Make sure to have a problem matcher defined.", JSON.stringify(taskId)); e({ severity: severity.Error, message: errorMessage }); </del> <ins> const errorMessage = nls.localize('taskNotTracked', "The task '{0}' has not exited and doesn't have a 'problemMatcher' defined. Make sure to define a problem matcher for watch tasks.", typeof taskId === 'string' ? taskId : JSON.stringify(taskId)); reject({ severity: severity.Error, message: errorMessage }); </ins> } <del> }, waitTime); }); </del> <ins> }, waitTime)); // Notification shown on any task taking a while to resolve store.add(disposableTimeout(() => { const message = nls.localize('runningTask', "Waiting for preLaunchTask '{0}'...", task.configurationProperties.name); const buttons = [DEBUG_ANYWAY_LABEL_NO_MEMO, ABORT_LABEL]; const canConfigure = task instanceof CustomTask || task instanceof ConfiguringTask; if (canConfigure) { buttons.splice(1, 0, nls.localize('configureTask', "Configure Task")); } this.progressService.withProgress( { location: ProgressLocation.Notification, title: message, buttons }, () => result.catch(() => { }), (choice) => { if (choice === undefined) { // no-op, keep waiting } else if (choice === 0) { // debug anyway resolve({ exitCode: 0 }); } else { // abort or configure resolve({ exitCode: undefined, cancelled: true }); this.taskService.terminate(task).catch(() => { }); if (canConfigure && choice === 1) { // configure this.taskService.openConfig(task as CustomTask); } } } ); }, 10_000)); })); </ins> }); <ins> return result.finally(() => store.dispose()); </ins> } }