MCPcopy
hub / github.com/coder/mux / exec

Function exec

src/node/runtime/LocalBaseRuntime.ts:54–210  ·  view source on GitHub ↗
(command: string, options: ExecOptions)

Source from the content-addressed store, hash-verified

52 */
53export abstract class LocalBaseRuntime implements Runtime {
54 async exec(command: string, options: ExecOptions): Promise<ExecStream> {
55 const startTime = performance.now();
56
57 if (options.abortSignal?.aborted) {
58 throw new RuntimeErrorClass("Operation aborted before execution", "exec");
59 }
60
61 // Use the specified working directory (must be a specific workspace path)
62 const cwd = options.cwd;
63
64 // Check if working directory exists before spawning
65 // This prevents confusing ENOENT errors from spawn()
66 try {
67 await fsPromises.access(cwd);
68 } catch (err) {
69 throw new RuntimeErrorClass(
70 `Working directory does not exist: ${cwd}`,
71 "exec",
72 err instanceof Error ? err : undefined
73 );
74 }
75
76 const bashPath = getBashPath();
77 const spawnCommand = bashPath;
78
79 // Match RemoteRuntime behavior: ensure non-interactive env vars are set inside the shell.
80 //
81 // Why not rely solely on `env`?
82 // - On Windows, env var casing and shell startup state can be surprising.
83 // - These are non-sensitive vars that we want to guarantee for git/editor safety.
84 const nonInteractivePrelude = Object.entries(NON_INTERACTIVE_ENV_VARS)
85 .map(([key, value]) => buildShellExport(key, value))
86 .join("\n");
87
88 const spawnArgs = ["-c", `${nonInteractivePrelude}\n${command}`];
89
90 const defaultPath = "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
91 const mergedEnv = sanitizeMuxChildEnv({ ...process.env, ...(options.env ?? {}) });
92 const basePath =
93 (options.env?.PATH && options.env.PATH.length > 0
94 ? mergedEnv.PATH
95 : (mergedEnv.PATH ?? mergedEnv.Path)) ?? defaultPath;
96 const effectivePath = basePath;
97
98 const childProcess = spawn(spawnCommand, spawnArgs, {
99 cwd,
100 env: {
101 ...mergedEnv,
102 ...NON_INTERACTIVE_ENV_VARS,
103 PATH: effectivePath,
104 },
105 stdio: ["pipe", "pipe", "pipe"],
106 // CRITICAL: Spawn as detached process group leader to enable cleanup of background processes.
107 // When a bash script spawns background processes (e.g., `sleep 100 &`), we need to kill
108 // the entire process group (including all backgrounded children) via process.kill(-pid).
109 // NOTE: detached:true does NOT cause bash to wait for background jobs when using 'exit' event
110 // instead of 'close' event. The 'exit' event fires when bash exits, ignoring background children.
111 detached: true,

Callers

nothing calls this directly

Calls 11

addCleanupMethod · 0.95
getBashPathFunction · 0.90
buildShellExportFunction · 0.90
sanitizeMuxChildEnvFunction · 0.90
killProcessTreeFunction · 0.90
forceCloseStdioFunction · 0.90
rejectFunction · 0.85
onMethod · 0.80
removeEventListenerMethod · 0.80
resolveFunction · 0.50
addEventListenerMethod · 0.45

Tested by

no test coverage detected