exec timeout --signal=TERM --kill-after="$1" "$2" python -m pytest "${@:3}"
I diagnosed the problem class and built a verified, self-contained solution at /workspace/solution/:
SOLUTION.md — the full write-uptools/run_pytest_arm.sh — the fixed pytest arm wrappertests/test_run_pytest_arm.sh — hermetic regression (passes)gate/run_gate.sh — wires the regression into the gateThe 1200 s budget signalled only the pytest process, not the process group. On a loaded host, a stalled test's descendants (xdist workers, servers, lock holders) either ignore SIGTERM and survive, or the gate parent dies and takes the budget owner with it — so orphans and locks leak and wedge the next run (REVIEW-AUGER-005 class).
PYTEST_BUDGET_SECS="${AUGER_PYTEST_BUDGET_SECS:-1200}"
PYTEST_KILL_AFTER_SECS="${AUGER_PYTEST_KILL_AFTER_SECS:-30}"
ARM="pytest"
tmp="$(mktemp -d)"; pgid_file="$tmp/pgid"; trap 'rm -rf "$tmp"' EXIT
# new session/PGID survives the gate parent; timeout owns the budget
setsid --wait bash -c '
echo "$$" > "$1"; shift
exec timeout --signal=TERM --kill-after="$1" "$2" python -m pytest "${@:3}"
' _ "$pgid_file" "$PYTEST_KILL_AFTER_SECS" "$PYTEST_BUDGET_SECS" "$@" &
child=$!; set +e; wait "$child"; rc=$?; set -e
if [[ "$rc" -eq 124 || "$rc" -eq 137 ]]; then
pgid="$(cat "$pgid_file")"
kill -TERM -- "-$pgid" 2>/dev/null || true; sleep 1
kill -KILL -- "-$pgid" 2>/dev/null || true
printf 'GATE-FAIL arm=%s reason=wall-budget-exceeded budget=%ss kill-after=%ss rc=%s\n' \
"$ARM" "$PYTEST_BUDGET_SECS" "$PYTEST_KILL_AFTER_SECS" "$rc" >&2
exit 124
fi
exit "$rc"
$ timeout 60 bash tests/test_run_pytest_arm.sh
ok: TERM-ignoring grandchild 302 was killed
wrapper output: GATE-FAIL arm=pytest reason=wall-budget-exceeded budget=1s kill-after=1s rc=137
PASS test_run_pytest_arm
TEST_RC=0
Also verified:
- Gate self-test runs first, then the real arm succeeds (GATE_RC=0).
- Exit codes pass through untouched (0 on pass, 3 on real failure) — only a budget trip becomes 124.
- After SIGKILL of the wrapper, the detached setsid budget owner stayed alive (t=2 yes) and exited on its own (t=6 no), proving the budget survives parent death.
- shellcheck -S warning clean.
Full root-cause analysis, the Python gate variant, the regression code, the gate wiring, acceptance-criteria mapping, and rollout notes are in /workspace/solution/SOLUTION.md.
# Evidence - Problem class: gate-pytest-arm-needs-process-group-budget - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-10-05T22:33:40.277Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Auger gate pytest arm on a loaded host exceeds 1200s (pre-existing live/lock hangs, REVIEW-AUGER-005 class). Fix: run pytest under setsid timeout --kill-after so the whole process group dies even when the gate parent is killed; map exit 124 to a loud arm-naming gate failure; add a hermetic wrapper regression wired into the gate itself. Commit bbd9daf, tier2 verdict COMPLETE.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "gate-pytest-arm-needs-process-group-budget", "provider": "openrouter", "solved_at": "2026-10-05T22:33:40.277Z", "version": ""}