( command: string, abortSignal: AbortSignal, shellType: ShellType, options?: ExecOptions, )
| 180 | * Creates a new shell process for each command execution |
| 181 | */ |
| 182 | export async function exec( |
| 183 | command: string, |
| 184 | abortSignal: AbortSignal, |
| 185 | shellType: ShellType, |
| 186 | options?: ExecOptions, |
| 187 | ): Promise<ShellCommand> { |
| 188 | const { |
| 189 | timeout, |
| 190 | onProgress, |
| 191 | preventCwdChanges, |
| 192 | shouldUseSandbox, |
| 193 | shouldAutoBackground, |
| 194 | onStdout, |
| 195 | } = options ?? {} |
| 196 | const commandTimeout = timeout || DEFAULT_TIMEOUT |
| 197 | |
| 198 | const provider = await resolveProvider[shellType]() |
| 199 | |
| 200 | const id = Math.floor(Math.random() * 0x10000) |
| 201 | .toString(16) |
| 202 | .padStart(4, '0') |
| 203 | |
| 204 | // Sandbox temp directory - use per-user directory name to prevent multi-user permission conflicts. |
| 205 | // tmpdir() honors $TMPDIR so non-/tmp environments (Termux/Android, containers) work out of the box. |
| 206 | const sandboxTmpDir = posixJoin( |
| 207 | process.env.CLAUDE_CODE_TMPDIR || tmpdir(), |
| 208 | getClaudeTempDirName(), |
| 209 | ) |
| 210 | |
| 211 | const { commandString: builtCommand, cwdFilePath } = |
| 212 | await provider.buildExecCommand(command, { |
| 213 | id, |
| 214 | sandboxTmpDir: shouldUseSandbox ? sandboxTmpDir : undefined, |
| 215 | useSandbox: shouldUseSandbox ?? false, |
| 216 | }) |
| 217 | |
| 218 | let commandString = builtCommand |
| 219 | |
| 220 | let cwd = pwd() |
| 221 | |
| 222 | // Recover if the current working directory no longer exists on disk. |
| 223 | // This can happen when a command deletes its own CWD (e.g., temp dir cleanup). |
| 224 | try { |
| 225 | await realpath(cwd) |
| 226 | } catch { |
| 227 | const fallback = getOriginalCwd() |
| 228 | logForDebugging( |
| 229 | `Shell CWD "${cwd}" no longer exists, recovering to "${fallback}"`, |
| 230 | ) |
| 231 | try { |
| 232 | await realpath(fallback) |
| 233 | setCwdState(fallback) |
| 234 | cwd = fallback |
| 235 | } catch { |
| 236 | return createFailedCommand( |
| 237 | `Working directory "${cwd}" no longer exists. Please restart Claude from an existing directory.`, |
| 238 | ) |
| 239 | } |
no test coverage detected