Exec reference
The cross-platform subprocess helper, and the seam that makes it fakeable.
For using it, see the subprocess seam.
realExec
Section titled “realExec”const realExec: ExecFn;Runs a command and resolves with its result. Never rejects for a process-level failure — a command that cannot start, times out, or exits nonzero all resolve with the relevant field set.
ExecFn
Section titled “ExecFn”type ExecFn = (cmd: string[], opts?: ExecOptions) => Promise<ExecResult>;The injection seam. realExec satisfies it, and so does any fake you write. Accept one in your own
functions and default it to realExec.
ExecOptions
Section titled “ExecOptions”interface ExecOptions { cwd?: string; timeoutMs?: number; env?: Record<string, string | undefined>; input?: string;}| Option | Default | Notes |
|---|---|---|
cwd |
the current process’s | |
timeoutMs |
60000 |
On expiry the promise settles with timedOut: true |
env |
inherited | Keys you omit are inherited. A value of undefined unsets the variable rather than setting it empty |
input |
none | Piped to stdin, then the stream is closed. EPIPE is swallowed |
ExecResult
Section titled “ExecResult”interface ExecResult { code: number | null; stdout: string; stderr: string; timedOut: boolean; spawnError?: string;}| Field | Meaning |
|---|---|
code |
Exit code, or null if the process was terminated by a signal |
stdout / stderr |
Captured output, UTF-8 decoded |
timedOut |
true when timeoutMs elapsed |
spawnError |
Present when the process could not start at all — the command was not found, or was not executable |
Check spawnError first, then timedOut, then code.
Behavior
Section titled “Behavior”- argv array, no shell.
shell: trueis never set, so there are no quoting hazards and no injection surface. Shell syntax such as|or>is not interpreted. npm shims still resolve on Windows. - UTF-8 across chunk boundaries. Both pipes use
setEncoding("utf8"), so a multi-byte character split across two reads decodes correctly rather than becoming replacement characters. - The timeout settles on the timer itself, not on the child’s
closeevent. A POSIX child that ignores SIGTERM cannot hang the caller. - stdin is closed after
inputis written, so a child waiting on end-of-input proceeds.
Two different default timeouts
Section titled “Two different default timeouts”| Where | Default |
|---|---|
realExec (opts.timeoutMs) |
60000 ms |
ClaudeCliProvider constructor |
180000 ms |
The provider passes its own value through to realExec, so the 60-second default applies only when
you call realExec directly.
Source of truth
Section titled “Source of truth”src/exec.ts and src/providers/types.ts. Behavior is pinned in test/unit/exec.test.ts,
including a 50,000-character multi-byte stdin round-trip, an unset-via-undefined case, and a
SIGTERM-ignoring child.