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

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

Evidenced one-shot execution requires Forcola 0.7's reviewed PR #97 run/2
contract and matching shim. Forcola 0.6 cannot establish these facts and is
rejected before invoking `run_evidenced/5`. Legacy run/stream callbacks
remain available.
New options pass through without global environment or application changes.
Native exit, cancellation reason, transport, cleanup confirmation/scope and
guard close are retained independently. Missing evidence remains unknown.
A confirmed process group never proves containment of escaped descendants.

Every `codex` invocation runs under forcola's Rust shim, which places
the CLI in its own process group and signals the whole group on timeout
or when the BEAM dies. One-shot runs send SIGINT first, then SIGTERM
after `interrupt_grace_ms` (default 2,000), then SIGKILL after
`kill_grace_ms`. Codex handles SIGINT by stopping the tool command it is
running; on SIGTERM alone it exits and leaves that command behind,
because codex puts each tool command and stdio MCP server in its own
process group (#119). Pass `interrupt_signal: :int` and
`interrupt_grace_ms` in `runner_options` to change the grace.

Streaming (`stream_lines/4`) still sends SIGTERM, then SIGKILL, with no
SIGINT step: `Forcola.Stream` does not accept interrupt options yet
(joshrotenberg/forcola#93). A codex tool command running when a stream
is halted or times out can outlive codex. The default
`CodexWrapper.Runner.Port` sends SIGINT first on both paths, but signals
only the `codex` process. Neither runner stops descendants that leave
codex's process group or ignore these signals; on Linux, forcola's
cgroup option is the backstop.

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.7.0` dependency floor includes
this fix, raw output observation and bounded one-shot termination evidence.

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.

---

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