# `CodexWrapper.Runner.Forcola`
[🔗](https://github.com/genagent/codex_wrapper_ex/blob/v0.5.3/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.3.5` floor on the optional
dependency (see `mix.exs`) guarantees the fix is present.

forcola requires a finite whole-run bound, so a command with no
`:timeout` runs under `forcola_default_timeout_ms` instead of
unbounded. See `effective_timeout/1`.

`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.

---

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