# ExecHandle
URL: /docs/core/api-reference/exec-handle

The object returned by sb.exec() and sb.execCode() — PromiseLike<ExecResult> with pipe(), stdout(), and result().



```ts
import type { ExecHandle, ExecResult } from "@alineo-labs/sandbox";
```

`ExecHandle` is returned by `sb.exec()` and `sb.execCode()`. It implements `PromiseLike<ExecResult>` — you can `await` it directly, pipe it, or consume it as an async generator.

## Consumption modes [#consumption-modes]

### await (direct) [#await-direct]

```ts
const { stdout, stderr, exitCode } = await sb.exec("node --version");
```

Resolves after the command completes. `stdout` is the full buffered stdout string.

### pipe() [#pipe]

```ts
await sb.exec("npm run build").pipe(process.stdout);
```

Streams stdout chunks to any writable with a `write(chunk: string)` method. Resolves when the command completes.

### stdout() [#stdout]

```ts
for await (const chunk of sb.exec("npm test").stdout()) {
  process.stdout.write(chunk);
}
```

Async generator yielding stdout chunks as they arrive.

### result() [#result]

```ts
const handle = sb.exec("npm test");
// ... other work ...
const { stdout, stderr, exitCode } = await handle.result();
```

Explicit promise form. Equivalent to `await handle`.

## ExecResult [#execresult]

```ts
interface ExecResult {
  stdout: string; // full stdout as a string
  stderr: string; // full stderr as a string
  exitCode: number; // process exit code (0 = success)
}
```

## Strict mode [#strict-mode]

By default, `exec()` throws `CommandError` if `exitCode !== 0` after the handle resolves. Pass `{ strict: false }` to get the result instead:

```ts
const { exitCode } = await sb.exec("test -f /etc/hosts", { strict: false });
```

See [Error handling](/docs/core/concepts/error-handling) for more on `CommandError`.

## Multiple consumers [#multiple-consumers]

An `ExecHandle` can only be consumed once — the underlying stream starts draining as soon as `sb.exec()`/`sb.execCode()` is called, in the `ExecHandle` constructor, whether or not anything ever consumes it. All consumption modes (`pipe`, `stdout`, `await`, `result`) share the same internal chunk buffer.

```ts
const handle = sb.exec("long-running-command");

// These share the same stream — start them before any awaiting
void handle.pipe(process.stdout); // stream as it runs
const { exitCode } = await handle.result(); // get result when done
```

## In the workflow builder [#in-the-workflow-builder]

The `SandboxBuilder` in `@alineo-labs/workflow` queues `exec()` calls but doesn't return `ExecHandle` instances — ops are executed during flush. Use `readFile(path, as)` to capture values into `vars`:

```ts
const { vars } = await workflow(client)
  .sandbox({ image: "node:22", resources: { cpu: "500m", memory: "256Mi" } }, (sb) => {
    sb.exec("node --version > /tmp/ver.txt");
    sb.readFile("/tmp/ver.txt", "version");
  })
  .result();

console.log(vars.version);
```
