Files
Bo e1fae0dae6 Make the engineering harness lean and add optional topic memory (#1116)
RPI now owns the authorized outcome through finish, with Plan and Memory
loaded only when useful. Known defects get direct repair, and evidence
can change the approach under unchanged acceptance. Fresh exact-content
validation remains required. Memory provides optional recall, mining and
curation of reviewed topic pages; specialists and the fixed-dispatch
adapter remain optional.

The change reconciles current documentation and generated skill
projections. It preserves native budget and permission authority, BD
work ownership, protected external evidence storage and the distinction
between a supported lesson and demonstrated later benefit. It adds no
scheduler, work store, Go command or evidence schema.

Validation: required local Go/build/vet/race checks, aggregate suite,
generated-output check and 72 gates pass. The complete 44-test executor
suite passes; its shared-deadline fixture now tolerates CI scheduling
jitter while still requiring deadline exhaustion and preventing a third
launch. Fresh author-distinct review passed all 112 changed paths with
no findings; all seven exact-head CI checks passed at bfce33cce. Native
restricted-source enforcement and reduced token use are not established
by this change.
2026-09-09 10:10:29 -04:00

870 lines
32 KiB
Bash

#!/usr/bin/env bats
# codex-exec-lib.bats — the one-shot contract for scripts/lib/codex-exec.sh.
# The runner must classify the three known failure modes (STALL /
# ECHO / MISSING-CODEX) into DISTINCT exit codes so a caller can tell NO-VERDICT
# apart from a genuine result. Every case here uses a STUB `codex` on PATH — the
# real codex binary is NEVER invoked.
setup() {
LIB="$BATS_TEST_DIRNAME/../../scripts/lib/codex-exec.sh"
[ -f "$LIB" ] || skip "lib not found: $LIB"
TMP="$(mktemp -d)"
mkdir -p "$TMP/bin"
export PATH="$TMP/bin:$PATH"
export TMPDIR="$TMP"
unset CODEX_EXEC_TIMEOUT CODEX_EXEC_DEADLINE_EPOCH
# Require a timeout binary for the STALL/timeout cases. Missing enforcement
# now fails closed; hang fixtures need the supported capability installed.
# NOTE: keep this a single `if` so setup's terminal exit status is always 0.
# A bare `command -v gtimeout ... && HAVE_TIMEOUT=1` as the last setup line
# returns non-zero on Linux CI (gtimeout is a macOS/coreutils name only),
# which bats treats as a setup FAILURE and errors every test in the file.
HAVE_TIMEOUT=0
if command -v timeout >/dev/null 2>&1 || command -v gtimeout >/dev/null 2>&1; then
HAVE_TIMEOUT=1
REAL_TIMEOUT="$(command -v timeout || command -v gtimeout)"
fi
}
teardown() { rm -rf "$TMP"; }
# Stop fixtures independently if a regression leaves a process behind. A zombie
# is already dead; only an executing survivor violates the cleanup contract.
assert_stopped() {
local pid="$1" state
state="$(ps -o stat= -p "$pid" 2>/dev/null)" || return 0
[[ "$state" == *Z* ]] && return 0
kill -KILL "$pid" 2>/dev/null || true
echo "surviving process: $pid ($state)" >&2
return 1
}
# --- stub factories -----------------------------------------------------------
# A stub codex that SUCCEEDS and prints a real answer WITH the `tokens used`
# marker a real run emits (so echo-detection does NOT mis-flag it).
stub_success() {
cat > "$TMP/bin/codex" <<'FAKE'
#!/usr/bin/env bash
echo "the answer is 42"
echo "tokens used: 1234"
exit 0
FAKE
chmod +x "$TMP/bin/codex"
}
# A stub codex that HANGS well past a tiny timeout (STALL via kill).
stub_hang() {
cat > "$TMP/bin/codex" <<'FAKE'
#!/usr/bin/env bash
sleep 30
FAKE
chmod +x "$TMP/bin/codex"
}
# A stub codex that ECHOES its prompt back (arg-prompt mode) with NO `tokens
# used` marker — the ECHO/WANDER failure the lib must catch fail-closed.
stub_echo() {
cat > "$TMP/bin/codex" <<'FAKE'
#!/usr/bin/env bash
# Reflect the LAST positional (the prompt) back verbatim — an echo, no marker.
prompt=""
for a in "$@"; do prompt="$a"; done
printf '%s' "$prompt"
exit 0
FAKE
chmod +x "$TMP/bin/codex"
}
# A CLI-shaped stub that rejects a leading-hyphen prompt unless the caller
# inserted the standard end-of-options marker first.
stub_option_parser() {
cat > "$TMP/bin/codex" <<'FAKE'
#!/usr/bin/env bash
seen_terminator=0
for arg in "$@"; do
if [ "$arg" = "--" ]; then seen_terminator=1; continue; fi
if [ "$seen_terminator" -eq 0 ] && [ "${arg#-}" != "$arg" ]; then
case "$arg" in exec|--skip-git-repo-check|--sandbox|-m|-C|-c) continue;; esac
fi
done
[ "$seen_terminator" -eq 1 ] || { echo 'missing option terminator' >&2; exit 2; }
[ "${!#}" = '--- canonical skill bytes' ] || { echo 'prompt mismatch' >&2; exit 3; }
printf 'accepted prompt\n'
printf 'tokens used: 1\n'
FAKE
chmod +x "$TMP/bin/codex"
}
# A stub codex that records how many times it was invoked and prints nothing.
stub_flat_then_success() {
cat > "$TMP/bin/codex" <<FAKE
#!/usr/bin/env bash
COUNT="$TMP/flat-count"
n=\$(cat "\$COUNT" 2>/dev/null || echo 0)
n=\$((n + 1)); echo "\$n" > "\$COUNT"
exit 0
FAKE
chmod +x "$TMP/bin/codex"
}
# --- (a) SUCCESS with the tokens marker ---------------------------------------
@test "(a) success with 'tokens used' marker -> exit 0, output on stdout" {
stub_success
run bash -c '
. "'"$LIB"'"
CODEX_EXEC_PROMPT_ARG="do the thing" CODEX_EXEC_TIMEOUT=10 codex_exec_guarded
'
[ "$status" -eq 0 ]
[[ "$output" == *"the answer is 42"* ]]
}
@test "(a2) caller can capture structured stdout without merging stderr" {
cat > "$TMP/bin/codex" <<'FAKE'
#!/usr/bin/env bash
printf '{"type":"turn.completed"}\n'
printf 'runtime warning\n' >&2
FAKE
chmod +x "$TMP/bin/codex"
run bash -c '
. "'"$LIB"'"
CODEX_EXEC_OUT_FILE="'"$TMP"'/stdout.jsonl" \
CODEX_EXEC_STDERR_FILE="'"$TMP"'/stderr.log" \
REVIEWER_MARKER="turn.completed" CODEX_EXEC_PROMPT_ARG="probe" \
CODEX_EXEC_TIMEOUT=10 codex_exec_guarded
'
[ "$status" -eq 0 ]
[ "$output" = "" ]
[ "$(cat "$TMP/stdout.jsonl")" = '{"type":"turn.completed"}' ]
[ "$(cat "$TMP/stderr.log")" = "runtime warning" ]
}
# --- (b) HANG -> STALL-TIMEOUT (exit 124) within budget -----------------------
@test "(b) a hung codex is killed and returns STALL-TIMEOUT (124) within budget" {
[ "$HAVE_TIMEOUT" -eq 1 ] || skip "no timeout/gtimeout on PATH"
stub_hang
start=$SECONDS
run bash -c '
. "'"$LIB"'"
CODEX_EXEC_PROMPT_ARG="do the thing" CODEX_EXEC_TIMEOUT=1 codex_exec_guarded
'
elapsed=$(( SECONDS - start ))
[ "$status" -eq 124 ]
# Killed at ~1s, never allowed to run the full 30s sleep.
[ "$elapsed" -lt 10 ]
}
# --- (c) ECHO-only -> ECHO (exit 125) -----------------------------------------
@test "(c) an echo-only run (output ~= prompt, no marker) returns ECHO (125)" {
stub_echo
# A prompt long enough that the reflected echo clears the 80% size band.
run bash -c '
. "'"$LIB"'"
CODEX_EXEC_PROMPT_ARG="review this large change and reply with a verdict; do not wander the filesystem" \
CODEX_EXEC_TIMEOUT=10 codex_exec_guarded
'
[ "$status" -eq 125 ]
}
@test "(c2) an arg prompt beginning with hyphens is protected by an option terminator" {
stub_option_parser
run bash -c '
. "'"$LIB"'"
CODEX_EXEC_PROMPT_ARG="--- canonical skill bytes" CODEX_EXEC_TIMEOUT=10 codex_exec_guarded
'
[ "$status" -eq 0 ]
[[ "$output" == *"accepted prompt"* ]]
}
# --- (d) MISSING codex -> MISSING (exit 2) ------------------------------------
@test "(d) a missing codex binary returns MISSING (2), a precondition not a result" {
# No stub installed AND point CODEX_EXEC_BIN at a name that does not exist.
run bash -c '
. "'"$LIB"'"
CODEX_EXEC_BIN="codex-does-not-exist-xyz" CODEX_EXEC_PROMPT_ARG="x" codex_exec_guarded
'
[ "$status" -eq 2 ]
[[ "$output" == *"MISSING DEPENDENCY"* ]]
}
# --- (e) flat 0-byte output is reported after exactly one call ----------------
@test "(e) a flat 0-byte run is not retried" {
stub_flat_then_success
run bash -c '
. "'"$LIB"'"
CODEX_EXEC_PROMPT_ARG="do the thing" CODEX_EXEC_TIMEOUT=10 codex_exec_guarded
'
[ "$status" -eq 124 ]
[ "$(cat "$TMP/flat-count")" -eq 1 ]
}
# --- fire-and-score caller: empty output on a clean exit is SUCCESS, not STALL -
@test "(f) EXPECT_OUTPUT=0: a clean exit-0 with empty output is SUCCESS, not a stall" {
# A stub that does real work but prints nothing (like a producer writing to disk).
cat > "$TMP/bin/codex" <<'FAKE'
#!/usr/bin/env bash
exit 0
FAKE
chmod +x "$TMP/bin/codex"
run bash -c '
. "'"$LIB"'"
CODEX_EXEC_EXPECT_OUTPUT=0 CODEX_EXEC_PROMPT_ARG="build it" CODEX_EXEC_TIMEOUT=10 \
codex_exec_guarded
'
[ "$status" -eq 0 ]
}
# --- genuine non-zero exit is preserved verbatim (not remapped) ----------------
@test "(g) a genuine codex non-zero exit is returned verbatim (e.g. 1), distinct from a stall" {
cat > "$TMP/bin/codex" <<'FAKE'
#!/usr/bin/env bash
echo "codex refused to run: not a trusted directory" >&2
exit 1
FAKE
chmod +x "$TMP/bin/codex"
run bash -c '
. "'"$LIB"'"
CODEX_EXEC_EXPECT_OUTPUT=0 CODEX_EXEC_PROMPT_ARG="x" CODEX_EXEC_TIMEOUT=10 codex_exec_guarded
'
# codex's own rc (1) is preserved — NOT collapsed to a fixed constant and NOT
# mistaken for a stall (124).
[ "$status" -eq 1 ]
[[ "$output" == *"runtime evidence, not a review verdict"* ]]
[[ "$output" != *"genuine reviewer failure"* ]]
}
# --- the distinct exit-code constants are defined and unique -------------------
@test "(h) the documented exit-code constants are defined and mutually distinct" {
run bash -c '
. "'"$LIB"'"
echo "$CODEX_EXEC_OK $CODEX_EXEC_MISSING $CODEX_EXEC_STALL_TIMEOUT $CODEX_EXEC_ECHO"
'
[ "$status" -eq 0 ]
[ "$output" = "0 2 124 125" ]
}
# --- the producer/membrane templates are byte-stable --------------------------
@test "(i) codex_exec_producer_template emits the historical byte-identical defaults" {
run bash -c '. "'"$LIB"'"; codex_exec_producer_template producer'
[ "$status" -eq 0 ]
# shellcheck disable=SC2016 # Assert literal parameters in the emitted template.
[ "$output" = 'timeout "$3" codex exec --skip-git-repo-check -C "$1" -s workspace-write "$2" >/dev/null 2>&1' ]
run bash -c '. "'"$LIB"'"; codex_exec_producer_template membrane'
[ "$status" -eq 0 ]
# shellcheck disable=SC2016 # Assert literal parameters in the emitted template.
[ "$output" = 'codex exec --skip-git-repo-check "$1" 2>/dev/null' ]
}
# --- CODEX_EXEC_WRAP: an external (filesystem-sealing) prefix around codex ----
# The probe harness seals reps with an OUTER macOS `sandbox-exec` profile. Codex's
# own seatbelt cannot nest inside it (sandbox_apply: Operation not permitted), so
# a wrapped rep must run codex with --dangerously-bypass-approvals-and-sandbox (the
# flag codex documents for exactly "externally sandboxed" use) instead of
# --sandbox <value>. Wrapping via CODEX_EXEC_BIN is NOT acceptable: it flips the
# metadata tool's coverage_eligible to false. Hence a dedicated prefix array.
# A stub codex that records its argv one-per-line to $TMP/codex-argv, then
# succeeds with the genuine-run marker.
stub_argv_recorder() {
cat > "$TMP/bin/codex" <<FAKE
#!/usr/bin/env bash
printf '%s\n' "\$@" > "$TMP/codex-argv"
echo "the answer is 42"
echo "tokens used: 1234"
exit 0
FAKE
chmod +x "$TMP/bin/codex"
}
# A stub wrapper (stands in for `sandbox-exec -p <profile>`): records its own argv
# to $TMP/wrapper-argv, drops its one flag, then execs the rest (the codex argv).
stub_wrapper() {
cat > "$TMP/bin/stub-wrapper" <<FAKE
#!/usr/bin/env bash
printf '%s\n' "\$@" > "$TMP/wrapper-argv"
[ "\$1" = "--flag" ] || { echo 'wrapper: expected --flag first' >&2; exit 97; }
shift
exec "\$@"
FAKE
chmod +x "$TMP/bin/stub-wrapper"
}
# A stub `timeout` (shadows the real one on PATH) that records its argv to
# $TMP/timeout-argv, drops the budget, then execs the rest — so the test can
# assert the exact prefix ORDER: timeout, then wrapper, then codex.
stub_timeout() {
cat > "$TMP/bin/timeout" <<FAKE
#!/usr/bin/env bash
# --foreground is probed functionally by the library, so the stub has to accept
# it the way GNU timeout does: `timeout --foreground 1 true` must exit 0.
if [ "\$1" = "--foreground" ]; then shift; fi
printf '%s\n' "\$@" > "$TMP/timeout-argv"
shift
exec "\$@"
FAKE
chmod +x "$TMP/bin/timeout"
}
@test "(wrap-a) CODEX_EXEC_WRAP unset: codex argv is byte-identical to the unwrapped shape" {
stub_argv_recorder
run bash -c '
. "'"$LIB"'"
CODEX_EXEC_EXTRA_ARGS=(--json --ephemeral)
CODEX_EXEC_SKIP_GIT_CHECK=1 CODEX_EXEC_SANDBOX=workspace-write \
CODEX_EXEC_MODEL=gpt-test CODEX_EXEC_DIR=/tmp/work \
CODEX_EXEC_PROMPT_ARG="do the thing" CODEX_EXEC_TIMEOUT=10 codex_exec_guarded
'
[ "$status" -eq 0 ]
expected="$(printf '%s\n' exec --skip-git-repo-check --sandbox workspace-write -m gpt-test -C /tmp/work --json --ephemeral -- "do the thing")"
[ "$(cat "$TMP/codex-argv")" = "$expected" ]
[ ! -e "$TMP/wrapper-argv" ]
}
@test "(wrap-b) CODEX_EXEC_WRAP set: wrapper runs first with its flag, then codex with --dangerously-bypass-approvals-and-sandbox and NO --sandbox" {
stub_argv_recorder
stub_wrapper
stub_timeout
run bash -c '
. "'"$LIB"'"
CODEX_EXEC_WRAP=("'"$TMP"'/bin/stub-wrapper" --flag)
CODEX_EXEC_EXTRA_ARGS=(--json --ephemeral)
CODEX_EXEC_SKIP_GIT_CHECK=1 CODEX_EXEC_SANDBOX=workspace-write \
CODEX_EXEC_MODEL=gpt-test CODEX_EXEC_DIR=/tmp/work \
CODEX_EXEC_PROMPT_ARG="do the thing" CODEX_EXEC_TIMEOUT=10 codex_exec_guarded
'
[ "$status" -eq 0 ]
[[ "$output" == *"the answer is 42"* ]]
# The wrapper is OUTERMOST and was exec'd with its flag first; the timeout
# wrapper sits inside it, so the sandbox is the outermost process.
[ -s "$TMP/wrapper-argv" ]
[ "$(sed -n 1p "$TMP/wrapper-argv")" = "--flag" ]
[ "$(sed -n 2p "$TMP/wrapper-argv")" = "$TMP/bin/timeout" ]
[ "$(sed -n 3p "$TMP/wrapper-argv")" = "--foreground" ]
[ "$(sed -n 4p "$TMP/wrapper-argv")" = "10" ]
[ "$(sed -n 5p "$TMP/wrapper-argv")" = "codex" ]
[ "$(sed -n 6p "$TMP/wrapper-argv")" = "exec" ]
# codex saw the bypass flag in place of --sandbox <value>; everything else intact.
expected="$(printf '%s\n' exec --skip-git-repo-check --dangerously-bypass-approvals-and-sandbox -m gpt-test -C /tmp/work --json --ephemeral -- "do the thing")"
[ "$(cat "$TMP/codex-argv")" = "$expected" ]
! grep -qx -- '--sandbox' "$TMP/codex-argv"
! grep -qx -- 'workspace-write' "$TMP/codex-argv"
grep -qx -- '--dangerously-bypass-approvals-and-sandbox' "$TMP/codex-argv"
}
@test "(wrap-c) the seal is outermost: order is wrapper, timeout --foreground, codex" {
# GNU timeout calls setpgid(0,0), so with timeout OUTSIDE the wrapper the
# reviewer and every child landed in timeout's process group and a caller
# reaping "the rep's group" signalled a group the rep was never in. The
# wrapper also has to be outermost for the timeout binary itself to run
# INSIDE the sandbox rather than being resolved from PATH outside it.
stub_argv_recorder
stub_wrapper
stub_timeout
run bash -c '
. "'"$LIB"'"
CODEX_EXEC_WRAP=("'"$TMP"'/bin/stub-wrapper" --flag)
CODEX_EXEC_PROMPT_ARG="do the thing" CODEX_EXEC_TIMEOUT=7 codex_exec_guarded
'
[ "$status" -eq 0 ]
[ -s "$TMP/wrapper-argv" ]
[ "$(sed -n 1p "$TMP/wrapper-argv")" = "--flag" ]
[ "$(sed -n 2p "$TMP/wrapper-argv")" = "$TMP/bin/timeout" ]
[ "$(sed -n 3p "$TMP/wrapper-argv")" = "--foreground" ]
[ "$(sed -n 4p "$TMP/wrapper-argv")" = "7" ]
[ "$(sed -n 5p "$TMP/wrapper-argv")" = "codex" ]
[ -s "$TMP/timeout-argv" ]
[ "$(sed -n 1p "$TMP/timeout-argv")" = "7" ]
[ "$(sed -n 2p "$TMP/timeout-argv")" = "codex" ]
[ "$(sed -n 3p "$TMP/timeout-argv")" = "exec" ]
[ -s "$TMP/codex-argv" ]
}
@test "(wrap-e) a timeout that refuses --foreground fails closed as MISSING" {
# Running without the flag would silently put the reviewer back in timeout's
# process group, which is the defect this whole ordering exists to close.
stub_argv_recorder
cat > "$TMP/bin/timeout" <<'FAKE'
#!/usr/bin/env bash
if [ "$1" = "--foreground" ]; then
printf 'timeout: unrecognized option --foreground\n' >&2
exit 125
fi
shift
exec "$@"
FAKE
chmod +x "$TMP/bin/timeout"
run bash -c '
. "'"$LIB"'"
CODEX_EXEC_PROMPT_ARG="do the thing" CODEX_EXEC_TIMEOUT=7 codex_exec_guarded
'
[ "$status" -eq 2 ]
[[ "$output" == *"MISSING-TIMEOUT"* ]]
[ ! -e "$TMP/codex-argv" ]
}
@test "(wrap-d) a wrapped run preserves the exit-code contract (genuine non-zero verbatim, stall 124)" {
stub_wrapper
cat > "$TMP/bin/codex" <<'FAKE'
#!/usr/bin/env bash
echo "codex refused to run" >&2
exit 5
FAKE
chmod +x "$TMP/bin/codex"
run bash -c '
. "'"$LIB"'"
CODEX_EXEC_WRAP=("'"$TMP"'/bin/stub-wrapper" --flag)
CODEX_EXEC_EXPECT_OUTPUT=0 CODEX_EXEC_PROMPT_ARG="x" CODEX_EXEC_TIMEOUT=10 codex_exec_guarded
'
[ "$status" -eq 5 ]
[ "$HAVE_TIMEOUT" -eq 1 ] || skip "no timeout/gtimeout on PATH"
stub_hang
run bash -c '
. "'"$LIB"'"
CODEX_EXEC_WRAP=("'"$TMP"'/bin/stub-wrapper" --flag)
CODEX_EXEC_PROMPT_ARG="x" CODEX_EXEC_TIMEOUT=1 codex_exec_guarded
'
[ "$status" -eq 124 ]
}
@test "(wrap-e) CODEX_EXEC_WRAP is codex-only: the agy adapter ignores it and keeps --sandbox" {
stub_wrapper
cat > "$TMP/bin/agy" <<FAKE
#!/usr/bin/env bash
printf '%s\n' "\$@" > "$TMP/agy-argv"
echo "VERDICT: CONFIRMED"
FAKE
chmod +x "$TMP/bin/agy"
run bash -c '
. "'"$LIB"'"
CODEX_EXEC_WRAP=("'"$TMP"'/bin/stub-wrapper" --flag)
REVIEWER=agy CODEX_EXEC_PROMPT_ARG="review this" CODEX_EXEC_TIMEOUT=10 codex_exec_guarded
'
[ "$status" -eq 0 ]
[ ! -e "$TMP/wrapper-argv" ]
grep -qx -- '--sandbox' "$TMP/agy-argv"
! grep -qx -- '--dangerously-bypass-approvals-and-sandbox' "$TMP/agy-argv"
}
@test "bounds: explicit four-argument control calls preserve their timeout" {
run bash -c '. "'"$LIB"'"; _codex_exec_control limits 1800 1024 "" ""'
[ "$status" -eq 0 ]
[[ "$output" == "1800 1024 "* ]]
}
@test "bounds: missing timeout and deadline prevent any launch" {
stub_argv_recorder
stub_timeout
run bash -c '. "'"$LIB"'"; unset CODEX_EXEC_TIMEOUT CODEX_EXEC_DEADLINE_EPOCH; CODEX_EXEC_PROMPT_ARG=x codex_exec_guarded'
[ "$status" -eq 2 ]
[[ "$output" == *"MISSING-LIMIT"* ]]
[ ! -e "$TMP/codex-argv" ]
[ ! -e "$TMP/timeout-argv" ]
}
@test "bounds: caller timeout above ten minutes reaches the wrapper unchanged" {
stub_argv_recorder
stub_timeout
run bash -c '. "'"$LIB"'"; unset CODEX_EXEC_DEADLINE_EPOCH; CODEX_EXEC_TIMEOUT=1800 CODEX_EXEC_PROMPT_ARG=x codex_exec_guarded'
[ "$status" -eq 0 ]
[ "$(sed -n 1p "$TMP/timeout-argv")" = "1800" ]
}
@test "bounds: deadline alone supplies its remaining time beyond ten minutes" {
stub_argv_recorder
stub_timeout
run bash -c '
. "'"$LIB"'"
unset CODEX_EXEC_TIMEOUT
export CODEX_EXEC_DEADLINE_EPOCH="$(/usr/bin/perl -MTime::HiRes=time -e "print time + 1800")"
CODEX_EXEC_PROMPT_ARG=x codex_exec_guarded
'
[ "$status" -eq 0 ]
/usr/bin/perl -e 'exit !($ARGV[0] > 1790 && $ARGV[0] <= 1800)' "$(sed -n 1p "$TMP/timeout-argv")"
}
@test "bounds: the earlier of caller timeout and deadline governs the invocation" {
stub_argv_recorder
stub_timeout
run bash -c '
. "'"$LIB"'"
export CODEX_EXEC_DEADLINE_EPOCH="$(/usr/bin/perl -MTime::HiRes=time -e "print time + 1800")"
CODEX_EXEC_TIMEOUT=900 CODEX_EXEC_PROMPT_ARG=x codex_exec_guarded
'
[ "$status" -eq 0 ]
[ "$(sed -n 1p "$TMP/timeout-argv")" = "900" ]
run bash -c '
. "'"$LIB"'"
export CODEX_EXEC_DEADLINE_EPOCH="$(/usr/bin/perl -MTime::HiRes=time -e "print time + 900")"
CODEX_EXEC_TIMEOUT=1800 CODEX_EXEC_PROMPT_ARG=x codex_exec_guarded
'
[ "$status" -eq 0 ]
/usr/bin/perl -e 'exit !($ARGV[0] > 890 && $ARGV[0] <= 900)' "$(sed -n 1p "$TMP/timeout-argv")"
}
@test "timeout argv helper: missing or invalid bounds fail before capability probe" {
stub_timeout
run bash -c '. "'"$LIB"'"; unset CODEX_EXEC_TIMEOUT CODEX_EXEC_DEADLINE_EPOCH; codex_exec_timeout_cmd'
[ "$status" -eq 3 ]
[[ "$output" == *"MISSING-LIMIT"* ]]
[ ! -e "$TMP/timeout-argv" ]
for invalid in '' 0 -1 NaN inf bad; do
run env CODEX_EXEC_TIMEOUT=900 bash -c '. "'"$LIB"'"; codex_exec_timeout_cmd "$1"' _ "$invalid"
[ "$status" -eq 3 ]
[[ "$output" == *"INVALID-LIMIT"* ]]
[ ! -e "$TMP/timeout-argv" ]
done
}
@test "timeout argv helper: positional and inherited budgets have no ten-minute cap" {
stub_timeout
for mode in positional inherited deadline; do
run bash -c '
. "'"$LIB"'"
unset CODEX_EXEC_TIMEOUT CODEX_EXEC_DEADLINE_EPOCH
case "$1" in
positional) CODEX_EXEC_TIMEOUT=2000 codex_exec_timeout_cmd 1800 ;;
inherited) CODEX_EXEC_TIMEOUT=1800 codex_exec_timeout_cmd ;;
deadline) CODEX_EXEC_DEADLINE_EPOCH="$(/usr/bin/perl -MTime::HiRes=time -e "print time + 1800")" codex_exec_timeout_cmd ;;
esac
' _ "$mode"
[ "$status" -eq 0 ]
[[ "$output" == "$TMP/bin/timeout --foreground "* ]]
/usr/bin/perl -e 'exit !($ARGV[0] > 1790 && $ARGV[0] <= 1800)' "${output##* }"
done
}
@test "timeout argv helper: preparation consumes the earlier deadline" {
stub_timeout
run bash -c '
. "'"$LIB"'"
codex_exec_timeout_bin() { sleep .2; printf "%s" "'"$TMP"'/bin/timeout"; }
CODEX_EXEC_TIMEOUT=1800 CODEX_EXEC_DEADLINE_EPOCH="$(/usr/bin/perl -MTime::HiRes=time -e "print time + 900")" codex_exec_timeout_cmd
'
[ "$status" -eq 0 ]
/usr/bin/perl -e 'exit !($ARGV[0] > 890 && $ARGV[0] < 899.9)' "${output##* }"
}
@test "bounds: invalid timeout and capture limits prevent reviewer launch" {
stub_argv_recorder
for setting in CODEX_EXEC_TIMEOUT CODEX_EXEC_MAX_OUTPUT_BYTES; do
for invalid in '' 0 -1 NaN inf 1e999 bad; do
run env CODEX_EXEC_TIMEOUT=10 CODEX_EXEC_DEADLINE_EPOCH=4102444800 "$setting=$invalid" bash -c '. "'"$LIB"'"; CODEX_EXEC_PROMPT_ARG=x codex_exec_guarded'
[ "$status" -eq 2 ]
[[ "$output" == *"INVALID-LIMIT"* ]]
[ ! -e "$TMP/codex-argv" ]
done
done
}
@test "bounds: invalid and expired inherited deadlines prevent launch" {
stub_argv_recorder
for deadline in '' 0 -1 NaN later; do
run env "CODEX_EXEC_DEADLINE_EPOCH=$deadline" bash -c '. "'"$LIB"'"; CODEX_EXEC_PROMPT_ARG=x codex_exec_guarded'
[ "$status" -eq 2 ]
[ ! -e "$TMP/codex-argv" ]
done
run env CODEX_EXEC_DEADLINE_EPOCH=1 bash -c '. "'"$LIB"'"; CODEX_EXEC_PROMPT_ARG=x codex_exec_guarded'
[ "$status" -eq 124 ]
[ ! -e "$TMP/codex-argv" ]
}
@test "bounds: missing timeout enforcement fails closed" {
stub_argv_recorder
run bash -c '. "'"$LIB"'"; CODEX_EXEC_TIMEOUT=10 CODEX_EXEC_TIMEOUT_BIN=/nonexistent/timeout CODEX_EXEC_PROMPT_ARG=x codex_exec_guarded'
[ "$status" -eq 2 ]
[[ "$output" == *"MISSING-TIMEOUT"* ]]
[ ! -e "$TMP/codex-argv" ]
}
@test "bounds: preparation expiry prevents launch without renewing the caller deadline" {
stub_argv_recorder
run timeout --kill-after=1 5 bash -c '
. "'"$LIB"'"
# Preparation consumes the same deadline as the eventual reviewer.
codex_exec_timeout_bin() { sleep .4; printf "%s" "'"$REAL_TIMEOUT"'"; }
CODEX_EXEC_TIMEOUT=.3 CODEX_EXEC_PROMPT_ARG=x codex_exec_guarded
'
[ "$status" -eq 124 ]
[[ "$output" == *"deadline expired before launch"* ]]
[ ! -e "$TMP/codex-argv" ]
}
@test "bounds: TERM resistant reviewer and child are killed with partial evidence" {
cat > "$TMP/bin/codex" <<'FAKE'
#!/usr/bin/env bash
trap '' TERM
echo $$ > "$TMPDIR/reviewer-pid"
bash -c 'trap "" TERM; echo $$ > "$TMPDIR/child-pid"; exec sleep 30' &
while [ ! -s "$TMPDIR/child-pid" ]; do sleep .01; done
printf 'partial result\n'
wait
FAKE
chmod +x "$TMP/bin/codex"
start=$SECONDS
# This fixture must reach the running-reviewer branch. A .3s budget can be
# consumed by capability probing alone; the preceding case proves that path.
run timeout --kill-after=1 5 bash -c '. "'"$LIB"'"; CODEX_EXEC_TIMEOUT=2 CODEX_EXEC_PROMPT_ARG=x codex_exec_guarded'
[ "$status" -eq 124 ]
[ $((SECONDS - start)) -lt 4 ]
[ -s "$TMP/reviewer-pid" ]
[ -s "$TMP/child-pid" ]
assert_stopped "$(cat "$TMP/reviewer-pid")"
assert_stopped "$(cat "$TMP/child-pid")"
[[ "$output" == *"partial result"* ]]
[[ "$output" == *"deadline expired; owned process group stopped"* ]]
}
@test "bounds: clean parent exit with children is degraded even when cleanup succeeds" {
cat > "$TMP/bin/codex" <<'FAKE'
#!/usr/bin/env bash
bash -c 'trap "" TERM; echo $$ > "$TMPDIR/child-pid"; exec sleep 30' &
while [ ! -s "$TMPDIR/child-pid" ]; do sleep .01; done
printf 'tokens used: 1\n'
exit 0
FAKE
chmod +x "$TMP/bin/codex"
start=$SECONDS
run timeout --kill-after=1 5 bash -c '. "'"$LIB"'"; CODEX_EXEC_TIMEOUT=3 CODEX_EXEC_PROMPT_ARG=x codex_exec_guarded'
[ "$status" -eq 122 ]
[ $((SECONDS - start)) -lt 3 ]
assert_stopped "$(cat "$TMP/child-pid")"
[[ "$output" == *"rep-survivor"* ]]
[[ "$output" == *"tokens used: 1"* ]]
[[ "$output" != *"genuine reviewer failure"* ]]
}
@test "bounds: silent producer descendants are degraded without inherited capture pipes" {
cat > "$TMP/bin/codex" <<'FAKE'
#!/usr/bin/env bash
sleep 30 </dev/null >/dev/null 2>&1 &
echo $! > "$TMPDIR/child-pid"
exit 0
FAKE
chmod +x "$TMP/bin/codex"
run timeout --kill-after=1 5 bash -c '. "'"$LIB"'"; CODEX_EXEC_TIMEOUT=3 CODEX_EXEC_EXPECT_OUTPUT=0 CODEX_EXEC_PROMPT_ARG=x codex_exec_guarded'
[ "$status" -eq 122 ]
[[ "$output" == *"rep-survivor"* ]]
assert_stopped "$(cat "$TMP/child-pid")"
}
@test "bounds: continuous stdout and stderr share one exact capture cap" {
cat > "$TMP/bin/codex" <<'FAKE'
#!/usr/bin/env bash
trap '' TERM
echo $$ > "$TMPDIR/reviewer-pid"
while :; do printf 'stdout data\n'; printf 'stderr data\n' >&2; done
FAKE
chmod +x "$TMP/bin/codex"
run timeout --kill-after=1 5 bash -c '
. "'"$LIB"'"
CODEX_EXEC_TIMEOUT=3 CODEX_EXEC_MAX_OUTPUT_BYTES=1024 \
CODEX_EXEC_OUT_FILE="'"$TMP"'/out" CODEX_EXEC_STDERR_FILE="'"$TMP"'/err" \
CODEX_EXEC_PROMPT_ARG=x codex_exec_guarded
'
[ "$status" -eq 123 ]
[ $(( $(wc -c < "$TMP/out") + $(wc -c < "$TMP/err") )) -eq 1024 ]
[[ "$output" == *"OUTPUT-LIMIT"* ]]
assert_stopped "$(cat "$TMP/reviewer-pid")"
}
@test "bounds: repeated invocations cannot renew a shared deadline" {
cat > "$TMP/bin/codex" <<'FAKE'
#!/usr/bin/env bash
printf 'invoked\n' >> "$TMPDIR/invocations"
# The first call succeeds immediately; the second must exhaust the shared
# deadline. Avoid requiring two nearly-one-second calls to fit in 1.5 seconds
# on a loaded CI worker. A third worker must never launch.
if [ "$(wc -l < "$TMPDIR/invocations" | tr -d ' ')" -gt 1 ]; then sleep 30; fi
printf 'tokens used: 1\n'
FAKE
chmod +x "$TMP/bin/codex"
run timeout --kill-after=1 12 bash -c '
. "'"$LIB"'"
export CODEX_EXEC_DEADLINE_EPOCH="$(/usr/bin/perl -MTime::HiRes=time -e "print time + 5")"
unset CODEX_EXEC_TIMEOUT
export CODEX_EXEC_PROMPT_ARG=x
codex_exec_guarded || exit 91
codex_exec_guarded; second=$?
codex_exec_guarded; third=$?
[ "$second" -eq 124 ] && [ "$third" -eq 124 ]
'
[ "$status" -eq 0 ]
[ "$(wc -l < "$TMP/invocations" | tr -d ' ')" -eq 2 ]
}
@test "bounds: cancellation of the sourced caller cleans workers and restores traps" {
cat > "$TMP/bin/codex" <<'FAKE'
#!/usr/bin/env bash
trap '' TERM
echo $$ > "$TMPDIR/reviewer-pid"
bash -c 'trap "" TERM; echo $$ > "$TMPDIR/child-pid"; exec sleep 30' &
printf 'partial before cancellation\n'
wait
FAKE
chmod +x "$TMP/bin/codex"
cat > "$TMP/caller.sh" <<'CALLER'
source "$1"
trap 'echo original-term-handler' TERM
original=$(trap -p TERM)
CODEX_EXEC_TIMEOUT=10 CODEX_EXEC_OUT_FILE="$TMPDIR/evidence" CODEX_EXEC_PROMPT_ARG=x codex_exec_guarded
result=$?
[ "$(trap -p TERM)" = "$original" ] || exit 92
exit "$result"
CALLER
run timeout --kill-after=1 5 bash -c '
bash "'"$TMP"'/caller.sh" "'"$LIB"'" & runner=$!
for attempt in {1..100}; do [ -s "'"$TMP"'/child-pid" ] && break; sleep .02; done
kill -TERM "$runner"
wait "$runner"
'
[ "$status" -eq 143 ]
grep -q 'partial before cancellation' "$TMP/evidence"
assert_stopped "$(cat "$TMP/reviewer-pid")"
assert_stopped "$(cat "$TMP/child-pid")"
}
@test "bounds: parent death also stops the owned worker group" {
cat > "$TMP/bin/codex" <<'FAKE'
#!/usr/bin/env bash
trap '' TERM
echo $$ > "$TMPDIR/reviewer-pid"
printf 'partial before parent death\n'
exec sleep 30
FAKE
chmod +x "$TMP/bin/codex"
run timeout --kill-after=1 5 bash -c '
bash -c '\''source "$1"; CODEX_EXEC_TIMEOUT=10 CODEX_EXEC_OUT_FILE="$TMPDIR/evidence" CODEX_EXEC_PROMPT_ARG=x codex_exec_guarded'\'' _ "'"$LIB"'" & runner=$!
for attempt in {1..100}; do [ -s "'"$TMP"'/reviewer-pid" ] && break; sleep .02; done
kill -KILL "$runner"
wait "$runner" || true
sleep .5
'
[ "$status" -eq 0 ]
assert_stopped "$(cat "$TMP/reviewer-pid")"
grep -q 'partial before parent death' "$TMP/evidence"
}
@test "bounds: missing default timeout and hanging capability probe prevent launch" {
stub_argv_recorder
mkdir "$TMP/no-timeout"
ln -s "$(command -v tr)" "$TMP/no-timeout/tr"
run env PATH="$TMP/no-timeout" CODEX_EXEC_BIN="$TMP/bin/codex" CODEX_EXEC_TIMEOUT=10 /bin/bash -c '. "'"$LIB"'"; CODEX_EXEC_PROMPT_ARG=x codex_exec_guarded'
[ "$status" -eq 2 ]
[[ "$output" == *"MISSING-TIMEOUT"* ]]
[ ! -e "$TMP/codex-argv" ]
cat > "$TMP/bin/timeout" <<'FAKE'
#!/usr/bin/env bash
trap '' TERM
echo $$ > "$TMPDIR/probe-pid"
exec sleep 30
FAKE
chmod +x "$TMP/bin/timeout"
start=$SECONDS
run "$REAL_TIMEOUT" --kill-after=1 5 bash -c '. "'"$LIB"'"; CODEX_EXEC_TIMEOUT=10 CODEX_EXEC_PROMPT_ARG=x codex_exec_guarded'
[ "$status" -eq 2 ]
[ $((SECONDS - start)) -lt 4 ]
[ ! -e "$TMP/codex-argv" ]
assert_stopped "$(cat "$TMP/probe-pid")"
}
@test "bounds: file and stdin prompt bytes survive bounded capture" {
cat > "$TMP/bin/codex" <<'FAKE'
#!/usr/bin/env bash
cat > "$TMPDIR/received-prompt"
printf 'tokens used: 1\n'
FAKE
chmod +x "$TMP/bin/codex"
printf 'file prompt\n\n' > "$TMP/prompt"
run bash -c '. "'"$LIB"'"; CODEX_EXEC_TIMEOUT=10 CODEX_EXEC_PROMPT_FILE="'"$TMP"'/prompt" CODEX_EXEC_PROMPT_ARG=ignored codex_exec_guarded'
[ "$status" -eq 0 ]
cmp "$TMP/prompt" "$TMP/received-prompt"
run bash -c '. "'"$LIB"'"; printf "stdin prompt\n\n" | CODEX_EXEC_TIMEOUT=10 codex_exec_guarded'
[ "$status" -eq 0 ]
[ "$(cat "$TMP/received-prompt")" = 'stdin prompt' ]
}
@test "bounds: cancelling the caller during a hanging capability probe prevents launch" {
stub_argv_recorder
cat > "$TMP/bin/timeout" <<'FAKE'
#!/usr/bin/env bash
trap '' TERM
echo $$ > "$TMPDIR/probe-pid"
exec sleep 30
FAKE
chmod +x "$TMP/bin/timeout"
run "$REAL_TIMEOUT" --kill-after=1 5 bash -c '
bash -c '\''source "$1"; CODEX_EXEC_TIMEOUT=10 CODEX_EXEC_PROMPT_ARG=x codex_exec_guarded'\'' _ "'"$LIB"'" & runner=$!
for attempt in {1..100}; do [ -s "'"$TMP"'/probe-pid" ] && break; sleep .02; done
kill -TERM "$runner"
wait "$runner" || true
sleep .5
'
[ "$status" -eq 0 ]
[ ! -e "$TMP/codex-argv" ]
assert_stopped "$(cat "$TMP/probe-pid")"
}
@test "bounds: unopened prompt FIFO expires before reviewer launch" {
stub_argv_recorder
mkfifo "$TMP/prompt-pipe"
run timeout --kill-after=1 5 bash -c '. "'"$LIB"'"; CODEX_EXEC_TIMEOUT=.3 CODEX_EXEC_PROMPT_FILE="'"$TMP"'/prompt-pipe" codex_exec_guarded'
[ "$status" -eq 124 ]
[[ "$output" == *"partial input preserved at"* ]]
[ ! -e "$TMP/codex-argv" ]
}
@test "bounds: capture limits do not limit reviewer workspace file writes" {
cat > "$TMP/bin/codex" <<'FAKE'
#!/usr/bin/env bash
dd if=/dev/zero of="$TMPDIR/work-product" bs=4096 count=8 2>/dev/null
printf 'tokens used: 1\n'
FAKE
chmod +x "$TMP/bin/codex"
run bash -c '. "'"$LIB"'"; CODEX_EXEC_TIMEOUT=10 CODEX_EXEC_MAX_OUTPUT_BYTES=32 CODEX_EXEC_PROMPT_ARG=x codex_exec_guarded'
[ "$status" -eq 0 ]
[ "$(wc -c < "$TMP/work-product" | tr -d ' ')" -eq 32768 ]
}
@test "bounds: default capture limit is finite and retains exactly ten MiB" {
cat > "$TMP/bin/codex" <<'FAKE'
#!/usr/bin/env bash
dd if=/dev/zero bs=1048576 count=11 2>/dev/null
FAKE
chmod +x "$TMP/bin/codex"
run "$REAL_TIMEOUT" --kill-after=1 5 bash -c '. "'"$LIB"'"; CODEX_EXEC_TIMEOUT=10 CODEX_EXEC_OUT_FILE="'"$TMP"'/evidence" CODEX_EXEC_PROMPT_ARG=x codex_exec_guarded'
[ "$status" -eq 123 ]
[ "$(wc -c < "$TMP/evidence" | tr -d ' ')" -eq 10485760 ]
}
@test "skill discovery caller: absent bounds fail before reviewer launch" {
stub_argv_recorder
run bash "$BATS_TEST_DIRNAME/../../scripts/validate-codex-cli-skills.sh" --workdir "$TMP"
[ "$status" -eq 1 ]
[[ "$output" == *"MISSING-LIMIT"* ]]
[ ! -e "$TMP/codex-argv" ]
}
@test "skill discovery caller: explicit timeout and deadline options reach the adapter" {
stub_timeout
cat > "$TMP/bin/codex" <<'FAKE'
#!/usr/bin/env bash
printf '%s\n' '{"type":"item.completed","item":{"type":"agent_message","text":"using-agentops,swarm,research"}}'
printf 'tokens used: 1\n'
FAKE
chmod +x "$TMP/bin/codex"
caller="$BATS_TEST_DIRNAME/../../scripts/validate-codex-cli-skills.sh"
run bash "$caller" --workdir "$TMP" --timeout 1800
[ "$status" -eq 0 ]
[[ "$output" == *"Required skills present"* ]]
[ "$(sed -n 1p "$TMP/timeout-argv")" = "1800" ]
deadline="$(/usr/bin/perl -MTime::HiRes=time -e 'print time + 1800')"
run bash "$caller" --workdir "$TMP" --deadline-epoch "$deadline"
[ "$status" -eq 0 ]
/usr/bin/perl -e 'exit !($ARGV[0] > 1790 && $ARGV[0] <= 1800)' "$(sed -n 1p "$TMP/timeout-argv")"
run bash "$caller" --workdir "$TMP" --timeout 900 --deadline-epoch "$deadline"
[ "$status" -eq 0 ]
[ "$(sed -n 1p "$TMP/timeout-argv")" = "900" ]
}