> ## Documentation Index
> Fetch the complete documentation index at: https://docs.obversa.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# process package

> Run one child process with a deadline and get a result you can read whatever the child did.

Every workflow runs child processes: a `git` call, a model CLI, a test
command. Each is the one thing that can wait forever, and a hang carries no
error. `@obversa/process` runs one child with a deadline and returns a
result you can read whatever happened: it finished, it hung, it filled a
pipe, or it ignored a signal.

```bash theme={null}
npm install @obversa/process
```

## What you get

* **A deadline that always means timeout.** When the time passes, the child
  is stopped and the result says so, even when the stopped child reports no
  exit code. By default only the child is signalled, so a process the child
  started and left behind is not stopped. Pass `detached: true` to stop the
  child's whole process group.
* **No pipe can stall it.** Standard input is closed after the optional
  input, and both output streams are drained until they close or for a
  short grace after the child exits, under one combined byte cap. The exit
  is the result; the pipes are a bounded extra.
* **A result, not a race.** The exit code, the output bytes and the
  timed-out and aborted flags come from facts the helper recorded, never
  from whichever callback fired first.

## Run one

```ts theme={null}
import { runChild } from '@obversa/process';

const result = await runChild({
  executable: process.execPath,
  args: ['-e', 'process.stdout.write("ready")'],
  cwd: process.cwd(),
  env: {},
  stdin: '',
  timeoutMs: 30_000,
  killGraceMs: 5_000,
  maxOutputBytes: 1_024 * 1_024,
});

console.log(new TextDecoder().decode(result.stdout));
```

```text theme={null}
ready
```

## The call

`runChild(options)` takes `executable`, `args`, `cwd`, `env`, an optional
`stdin`, `timeoutMs`, `killGraceMs`, `maxOutputBytes`, an optional
`AbortSignal`, and `inheritParentEnv` (default true). It returns
`exitCode` (a number, or `null` when a signal stopped the child), `stdout`
and `stderr` as bytes, `timedOut` and `aborted`. It throws `RunChildError`
with code `SPAWN_FAILED` when the executable cannot start,
`OUTPUT_LIMIT` when the child writes past the cap, and
`TEARDOWN_INCOMPLETE` when the child does not stop within the grace period
after its deadline.

## When your process dies

Children the helper started are stopped when your process exits or is
interrupted, the way the signal-exit library does it: a terminate signal on
exit, and on an interrupt the helper stops its children and re-raises the
signal so your process ends as it would have. Your own handler for a
signal takes precedence. A child sits in your process group by default so
a terminal interrupt reaches it; `detached: true` gives it its own group.

## Things that catch people out

* **Bytes written after the grace are not captured**; the result holds
  what arrived before the child exited plus the short drain.
* **The output cap is combined** across standard output and standard error.
* **The environment is merged** over the parent's unless you say otherwise.
* **A timed-out child still has output**: what it wrote before the deadline
  is in the result, up to the cap.

## Where it is used

The engine command runner, the runtime's git and command sites, and the Git
memory adapter run their children through this package, so a hang in any
of them ends the same way, with a timeout you can read.
