I am trying to create a simple VSCode extension to run a set of commands when I open a folder. Basically these commands will set up our development environment. I have started off creating the boilerplace and ran through the example that VSCode provided but I am not clear how to run system commands. Appreciate any help or point me to some documentation about this topic.
4 Answers
Your extension environment has access to node.js libraries, so you can just use child_process
or any helper libraries to execute commands:
const cp = require('child_process')
cp.exec('pwd', (err, stdout, stderr) => {
console.log('stdout: ' + stdout);
console.log('stderr: ' + stderr);
if (err) {
console.log('error: ' + err);
}
});

- 31,714
- 9
- 78
- 100

- 58,117
- 21
- 175
- 206
-
16This is the way we should do such a thing? There is no VSCode graceful API for that? like what if the user closes VSCode abruptly? This means that maybe my extension external process will run forever? – Michael Mar 11 '18 at 00:37
-
2No, it's a child_process, in which case when the VSCode process terminates, so will any child process. – MatBee Nov 01 '19 at 16:48
-
5@MatBee Sadly, that is not how child processes work. See: https://stackoverflow.com/questions/8533377/why-child-process-still-alive-after-parent-process-was-killed-in-linux – Domi Jul 07 '20 at 11:32
-
See [Child process.options.detached](https://nodejs.org/api/child_process.html#child_process_options_detached) – Matt Bierner Jul 07 '20 at 19:45
-
@MattBierner It becomes a mess that ends up being very non-cross-plattform, as you can read in the documentation you linked yourself, quoting: "On non-Windows platforms, [...] Child processes may continue running after the parent exits regardless of whether they are detached or not." – Domi Sep 16 '20 at 21:50
-
Is there a way to do this but the output ends up in the Built-in VS Code Terminal, (or a new one created with `vscode.window.createTerminal('Terminal Name');`).??? – SamTheProgrammer Jun 21 '21 at 18:09
-
@Password-Classified There is - see the other answer. – Domi Jul 21 '21 at 16:20
-
@Domi Thank you. I didn't see that. – SamTheProgrammer Jul 21 '21 at 16:31
One alternative could be to use the Terminal API which is the best option if you have the need for the process to be fully observable and controllable to/by the user.
Biggest downside: The Terminal API does not yet offer a way to introspect the processes that run inside of it.
If you actually want to run the process in the terminal, the only way to do this safely for now, would be to use a two-layer approach, where you start a wrapper process that in turn launches and observes the actual process (taken in via command line args).
Our self-made TerminalWrapper
We tried this ourselves.
In our first approach, the wrapper used a
socket.io
connection that allows for communication with and control by the extension.In our second approach, we simplified and instead created the terminal using
bash -c
(non-interactive shell), and used a file watcher to get the results instead. Easier this way but after the process is done, the user won't be able to use the Terminal window (because its non-interactive). A lot less error-prone and does not require fulfilling socket.io dependency.
Implementation Details
- In our extension, we use a
TerminalWrapper
which runs the command inside a wrapper process, and waits for a file to contain the results. - The wrapper process is here. It writes the result to a file.
- Usage example here:
const cwd = '.';
const command = `node -e "console.log('hi!');"`;
const { code } = await TerminalWrapper.execInTerminal(cwd, command, {}).waitForResult();
if (code) {
const processExecMsg = `${cwd}$ ${command}`;
throw new Error(`Process failed with exit code ${code} (${processExecMsg})`);
}
Biggest downside with the second approach is that we now need bash
to be present, however (i) we do have a dependency checker that will warn you if you don't and explain how to get it, and (ii) using a unified shell, makes running commands a lot easier, as we now have a pretty strong unified feature set, we know we can rely on, rather than only being able to use common command execution syntax, and (iii) we can even run *.sh
files without having to worry.
Introducing: VSCode Terminal API
All of the following imagery and excerpts are just straight up copy-and-paste'd from their official sample repository:
Create a terminal and run a command in it
context.subscriptions.push(vscode.commands.registerCommand('terminalTest.createAndSend', () => {
const terminal = vscode.window.createTerminal(`Ext Terminal #${NEXT_TERM_ID++}`);
terminal.sendText("echo 'Sent text immediately after creating'");
}));
Terminal activation event
vscode.window.onDidChangeActiveTerminal(e => {
console.log(`Active terminal changed, name=${e ? e.name : 'undefined'}`);
});
TerminalQuickPickItem
function selectTerminal(): Thenable<vscode.Terminal | undefined> {
interface TerminalQuickPickItem extends vscode.QuickPickItem {
terminal: vscode.Terminal;
}
const terminals = <vscode.Terminal[]>(<any>vscode.window).terminals;
const items: TerminalQuickPickItem[] = terminals.map(t => {
return {
label: `name: ${t.name}`,
terminal: t
};
});
return vscode.window.showQuickPick(items).then(item => {
return item ? item.terminal : undefined;
});
}
...and a lot more!...
(<3 for the VSCode team and their hard work.)

- 22,151
- 15
- 92
- 122
-
1
-
The Terminal API is currently very limited. It does not allow you query anything about its state, other than whether or not it is open or not, its PID, dimensions and other things you see in the screenshot above. You can even fetch the output using the `onDidWriteTerminalData` event (however that will probably never be part of the stable API as discussed [here](https://github.com/microsoft/vscode/issues/83224)). Sadly there is no way to know if or what is currently running inside of a terminal, unless you wrap your command in an observer application as I propopsed above. – Domi Aug 17 '20 at 14:48
What I did was to create a promise based utility function to run all shell command with child_process
import * as cp from "child_process";
const execShell = (cmd: string) =>
new Promise<string>((resolve, reject) => {
cp.exec(cmd, (err, out) => {
if (err) {
return reject(err);
}
return resolve(out);
});
});
To get current directory
const currentDir = await execShell('pwd');
To get current git branch name
const branchName = await execShell('git rev-parse --abbrev-ref HEAD');

- 1,252
- 2
- 15
- 22

- 5,812
- 2
- 12
- 13
In my case, it was mandatory that I execute my commands within the user terminal. But I wanted to get notified on command success or failure. It is less generic but gives me more control.
import * as vscode from 'vscode';
import * as fs from 'fs';
async function executeAndRead(command: string): Promise<string> {
ensureTerminalExists();
const terminal = await selectTerminal();
if (terminal) {
terminal.sendText(command + ` > ${outputFile} || pwd > ${triggerFile}`);
return waitForFileUpdate(outputFile, triggerFile);
}
return Promise.reject('Could not select terminal');
}
async function waitForFileUpdate(outputFile: string, triggerFile: string): Promise<string> {
return new Promise<string>((resolve, reject) => {
const watcher = fs.watch(triggerFile);
watcher.on('change', () => {
watcher.close();
fs.readFile(outputFile, 'utf8', (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
watcher.on('error', reject);
});
}
The idea is that I'm modifying two files in sequence. The second is mostly a dummy. But I know that first file has finished updating when I get the 2nd file's trigger.

- 1,659
- 16
- 20
-
There is race condition in some cases when executing multiple commands in quick succession. In that case, I output an increasing serial number to the trigger file and wait for that serial number to be available before reading data file. – anandbibek May 24 '23 at 05:08