# `CodexWrapper.Runner.Forcola`
[🔗](https://github.com/genagent/codex_wrapper_ex/blob/v0.6.0/lib/codex_wrapper/runner/forcola.ex#L2)

Leak-free runner backed by [forcola](https://hex.pm/packages/forcola).

Every `codex` invocation runs under forcola's Rust shim, which places
the CLI in its own process group and kills the whole group (SIGTERM,
then SIGKILL) on timeout or when the BEAM dies. That reaps `codex` and
every stdio MCP server it spawned together, where the default
`CodexWrapper.Runner.Port` would leave them running as orphans (see
#48 and closed #33).

forcola closes the child's stdin immediately after spawn (forcola
0.3.4, forcola#67), so `codex` sees EOF naturally -- the `/bin/sh
... < /dev/null` wrapper the default runner needs is unnecessary
here. Before 0.3.4 forcola left the child's stdin open, and `codex
exec` would print "Reading additional input from stdin..." and hang
until the timeout elapsed. The `~> 0.6.0` dependency floor includes
this fix and raw output observation for one-shot execution.

forcola requires a finite whole-run bound. Synchronous commands with
no `:timeout` use `forcola_default_timeout_ms` (five minutes by
default); streams use `forcola_default_stream_timeout_ms` (one hour
by default), so a long active turn is not cut off after five minutes.

`stream_lines/4` is backed by `Forcola.Stream.lines/2`, so the
NDJSON paths (`Exec.stream/2` and friends) get the same group kill
on halt, timeout, or BEAM death. Unlike `Forcola.Stream.lines/2`,
it does not raise on a non-zero exit -- it ends the stream, which is
what `CodexWrapper.Runner.Port` has always done and what the
`CodexWrapper.Runner` contract specifies.

This module compiles only when `forcola` is a dependency. Select it
with `config :codex_wrapper, runner: CodexWrapper.Runner.Forcola`
(or the `:forcola` shorthand). forcola is POSIX-only.

# `effective_timeout`

The bound forcola will enforce for `timeout`.

A caller's `nil` ("no timeout") becomes
`config :codex_wrapper, forcola_default_timeout_ms: <ms>`
(default `300000`), since forcola requires a finite bound.

# `run_observed`

```elixir
@spec run_observed(
  String.t(),
  [String.t()],
  keyword(),
  timeout() | nil,
  {pid(), reference()}
) ::
  {:ok, {binary(), non_neg_integer(), binary()}}
  | {:error, CodexWrapper.Runner.error()}
```

Observe raw output while preserving the terminal one-shot outcome.

Requires Forcola's `:output_observer` support. The execution caller
receives raw chunks from its owned helper before this function returns.
Stderr stays separate.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
