src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts/TerminalProcessManager/createProcess
class TerminalProcessManager:	async createProcess(
		shellLaunchConfig: IShellLaunchConfig,
		cols: number,
		rows: number,
		isScreenReaderModeEnabled: boolean,
		reset: boolean = true
	): Promise<ITerminalLaunchError | undefined> {
		this._shellLaunchConfig = shellLaunchConfig;
		this._dimensions.cols = cols;
		this._dimensions.rows = rows;
		this._isScreenReaderModeEnabled = isScreenReaderModeEnabled;

		let newProcess: ITerminalChildProcess | undefined;

		if (shellLaunchConfig.customPtyImplementation) {
			this._processType = ProcessType.PsuedoTerminal;
			newProcess = shellLaunchConfig.customPtyImplementation(this._instanceId, cols, rows);
		} else {
			const backend = await this._terminalInstanceService.getBackend(this.remoteAuthority);
			if (!backend) {
				throw new Error(`No terminal backend registered for remote authority '${this.remoteAuthority}'`);
			}
			this.backend = backend;

			// Create variable resolver
			const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot();
			const lastActiveWorkspace = activeWorkspaceRootUri ? withNullAsUndefined(this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri)) : undefined;
			const variableResolver = terminalEnvironment.createVariableResolver(lastActiveWorkspace, await this._terminalProfileResolverService.getEnvironment(this.remoteAuthority), this._configurationResolverService);

			// resolvedUserHome is needed here as remote resolvers can launch local terminals before
			// they're connected to the remote.
			this.userHome = this._pathService.resolvedUserHome?.fsPath;
			this.os = OS;
			if (!!this.remoteAuthority) {

				const userHomeUri = await this._pathService.userHome();
				this.userHome = userHomeUri.path;
				const remoteEnv = await this._remoteAgentService.getEnvironment();
				if (!remoteEnv) {
					throw new Error(`Failed to get remote environment for remote authority "${this.remoteAuthority}"`);
				}
				this.userHome = remoteEnv.userHome.path;
				this.os = remoteEnv.os;

				// this is a copy of what the merged environment collection is on the remote side
				const env = await this._resolveEnvironment(backend, variableResolver, shellLaunchConfig);
				const shouldPersist = ((this._configurationService.getValue(TaskSettingId.Reconnection) && shellLaunchConfig.reconnectionProperties) || !shellLaunchConfig.isFeatureTerminal) && this._configHelper.config.enablePersistentSessions && !shellLaunchConfig.isTransient;
				if (shellLaunchConfig.attachPersistentProcess) {
					const result = await backend.attachToProcess(shellLaunchConfig.attachPersistentProcess.id);
					if (result) {
						newProcess = result;
					} else {
						// Warn and just create a new terminal if attach failed for some reason
						this._logService.warn(`Attach to process failed for terminal`, shellLaunchConfig.attachPersistentProcess);
						shellLaunchConfig.attachPersistentProcess = undefined;
					}
				}
				if (!newProcess) {
					await this._terminalProfileResolverService.resolveShellLaunchConfig(shellLaunchConfig, {
						remoteAuthority: this.remoteAuthority,
						os: this.os
					});
					const options: ITerminalProcessOptions = {
						shellIntegration: {
							enabled: this._configurationService.getValue(TerminalSettingId.ShellIntegrationEnabled)
						},
						windowsEnableConpty: this._configHelper.config.windowsEnableConpty && !isScreenReaderModeEnabled,
						environmentVariableCollections: this._extEnvironmentVariableCollection?.collections ? serializeEnvironmentVariableCollections(this._extEnvironmentVariableCollection.collections) : undefined
					};
					try {
						newProcess = await backend.createProcess(
							shellLaunchConfig,
							'', // TODO: Fix cwd
							cols,
							rows,
							this._configHelper.config.unicodeVersion,
							env, // TODO:
							options,
							shouldPersist
						);
					} catch (e) {
						if (e?.message === 'Could not fetch remote environment') {
							this._logService.trace(`Could not fetch remote environment, silently failing`);
							return undefined;
						}
						throw e;
					}
				}
				if (!this._isDisposed) {
					this._setupPtyHostListeners(backend);
				}
			} else {
				if (shellLaunchConfig.attachPersistentProcess) {
					const result = shellLaunchConfig.attachPersistentProcess.findRevivedId ? await backend.attachToRevivedProcess(shellLaunchConfig.attachPersistentProcess.id) : await backend.attachToProcess(shellLaunchConfig.attachPersistentProcess.id);
					if (result) {
						newProcess = result;
					} else {
						// Warn and just create a new terminal if attach failed for some reason
						this._logService.warn(`Attach to process failed for terminal`, shellLaunchConfig.attachPersistentProcess);
						shellLaunchConfig.attachPersistentProcess = undefined;
					}
				}
				if (!newProcess) {
					newProcess = await this._launchLocalProcess(backend, shellLaunchConfig, cols, rows, this.userHome, isScreenReaderModeEnabled, variableResolver);
				}
				if (!this._isDisposed) {
					this._setupPtyHostListeners(backend);
				}
			}
		}

		// If the process was disposed during its creation, shut it down and return failure
		if (this._isDisposed) {
			newProcess.shutdown(false);
			return undefined;
		}

		this._process = newProcess;
		this._setProcessState(ProcessState.Launching);

		// Add any capabilities inherit to the backend
		if (this.os === OperatingSystem.Linux || this.os === OperatingSystem.Macintosh) {
			this.capabilities.add(TerminalCapability.NaiveCwdDetection, new NaiveCwdDetectionCapability(this._process));
		}

		this._dataFilter.newProcess(this._process, reset);

		if (this._processListeners) {
			dispose(this._processListeners);
		}
		this._processListeners = [
			newProcess.onProcessReady((e: IProcessReadyEvent) => {
				this.shellProcessId = e.pid;
				this._initialCwd = e.cwd;
				this._onDidChangeProperty.fire({ type: ProcessPropertyType.InitialCwd, value: this._initialCwd });
				this._onProcessReady.fire(e);

				if (this._preLaunchInputQueue.length > 0 && this._process) {
					// Send any queued data that's waiting
					newProcess!.input(this._preLaunchInputQueue.join(''));
					this._preLaunchInputQueue.length = 0;
				}
			}),
			newProcess.onProcessExit(exitCode => this._onExit(exitCode)),
			newProcess.onDidChangeProperty(({ type, value }) => {
				switch (type) {
					case ProcessPropertyType.HasChildProcesses:
						this._hasChildProcesses = value;
						break;
					case ProcessPropertyType.FailedShellIntegrationActivation:
						this._telemetryService?.publicLog2<{}, { owner: 'meganrogge'; comment: 'Indicates shell integration was not activated because of custom args' }>('terminal/shellIntegrationActivationFailureCustomArgs');
						break;
				}
				this._onDidChangeProperty.fire({ type, value });
			})
		];
		if (newProcess.onRestoreCommands) {
			this._processListeners.push(newProcess.onRestoreCommands(e => {
				this._onRestoreCommands.fire(e);
			}));
		}
		setTimeout(() => {
			if (this.processState === ProcessState.Launching) {
				this._setProcessState(ProcessState.Running);
			}
		}, LAUNCHING_DURATION);

		const result = await newProcess.start();
		if (result) {
			// Error
			return result;
		}
		return undefined;
	}