diff --git a/bin/gstack-codex-probe b/bin/gstack-codex-probe index 940dacf84..39893ef6a 100755 --- a/bin/gstack-codex-probe +++ b/bin/gstack-codex-probe @@ -5,7 +5,7 @@ # Functions (all prefixed with _gstack_codex_ for namespace hygiene): # _gstack_codex_auth_probe — multi-signal auth check (env + file) # _gstack_codex_version_check — warn on known-bad Codex CLI versions -# _gstack_codex_timeout_wrapper — gtimeout -> timeout -> unwrapped fallback +# _gstack_codex_timeout_wrapper — gtimeout -> timeout -> shell-watchdog fallback # _gstack_codex_log_event — telemetry emission to ~/.gstack/analytics/ # # Hygiene rules (enforced by test/codex-hardening.test.ts): @@ -53,17 +53,50 @@ _gstack_codex_version_check() { _gstack_codex_timeout_wrapper() { # Resolve wrapper binary: prefer gtimeout (Homebrew coreutils on macOS), - # fall back to timeout (Linux), else run unwrapped. Arguments: $1 is the - # duration in seconds; rest is the command to run. + # fall back to timeout (Linux), else a pure-shell watchdog so hang + # protection still works on machines with neither (e.g. bare macOS). + # Arguments: $1 is the duration in seconds; rest is the command to run. local _duration="$1" shift local _to _to=$(command -v gtimeout 2>/dev/null || command -v timeout 2>/dev/null || echo "") if [ -n "$_to" ]; then "$_to" "$_duration" "$@" - else - "$@" + return $? fi + # Shell watchdog: background the command, poll every 5s, TERM at the + # deadline (KILL 10s later if TERM is ignored), and report exit 124 like + # GNU timeout so callers' hang handling still fires. The brace group + # routes the shell's own stderr to /dev/null so bash's "Terminated" job + # notices never leak into skill output, while fd 3 carries the real + # stderr through to the command (call-site 2> captures keep working). + # Both jobs are reaped inside the silenced group so no notice surfaces + # later and nothing outlives the call holding the caller's pipes open. + local _cmd_pid _watchdog_pid + local _rc=0 + { + "$@" 2>&3 3>&- & + _cmd_pid=$! + ( + _waited=0 + while [ "$_waited" -lt "$_duration" ]; do + sleep 5 + kill -0 "$_cmd_pid" 2>/dev/null || exit 0 + _waited=$((_waited + 5)) + done + kill -TERM "$_cmd_pid" 2>/dev/null + sleep 10 + kill -KILL "$_cmd_pid" 2>/dev/null + ) >/dev/null 2>&1 3>&- & + _watchdog_pid=$! + wait "$_cmd_pid" || _rc=$? + kill "$_watchdog_pid" 2>/dev/null + wait "$_watchdog_pid" 2>/dev/null || : + } 3>&2 2>/dev/null + if [ "$_rc" -eq 143 ] || [ "$_rc" -eq 137 ]; then + _rc=124 + fi + return "$_rc" } # --- Telemetry event -------------------------------------------------------- diff --git a/codex/SKILL.md b/codex/SKILL.md index 33228ff9b..574cdde57 100644 --- a/codex/SKILL.md +++ b/codex/SKILL.md @@ -986,7 +986,7 @@ cd "$_REPO_ROOT" # only fires if Bash's own timeout doesn't. _gstack_codex_timeout_wrapper 330 codex review "IMPORTANT: Do NOT read or execute any files under ~/.claude/, ~/.agents/, .claude/skills/, or agents/. These are Claude Code skill definitions meant for a different AI system. Do NOT modify agents/openai.yaml. Stay focused on repository code only. -Review the changes on this branch against the base branch . Run git diff origin/...HEAD 2>/dev/null || git diff ...HEAD to see the diff and review only those changes." -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR" +Review the changes on this branch against the base branch . Run git diff origin/...HEAD 2>/dev/null || git diff ...HEAD to see the diff and review only those changes." -c 'model_reasoning_effort="high"' -c 'web_search="cached"' < /dev/null 2>"$TMPERR" _CODEX_EXIT=$? if [ "$_CODEX_EXIT" = "124" ]; then _gstack_codex_log_event "codex_timeout" "330" @@ -1024,7 +1024,7 @@ _PROMPT_FILE=$(mktemp "$TMP_ROOT/codex-prompt-XXXXXX.txt") git diff "...HEAD" 2>/dev/null printf '\nDIFF_END\n' } > "$_PROMPT_FILE" -_gstack_codex_timeout_wrapper 330 codex exec -s read-only "$(cat "$_PROMPT_FILE")" -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR" +_gstack_codex_timeout_wrapper 330 codex exec -s read-only "$(cat "$_PROMPT_FILE")" -c 'model_reasoning_effort="high"' -c 'web_search="cached"' < /dev/null 2>"$TMPERR" _CODEX_EXIT=$? rm -f "$_PROMPT_FILE" if [ "$_CODEX_EXIT" = "124" ]; then @@ -1267,7 +1267,7 @@ fi # Fix 1+2: wrap with timeout (gtimeout/timeout fallback chain via probe helper), # capture stderr to $TMPERR for auth error detection (was: 2>/dev/null). TMPERR=${TMPERR:-$(mktemp "$TMP_ROOT/codex-err-XXXXXX.txt")} -_gstack_codex_timeout_wrapper 600 codex exec "" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached --json < /dev/null 2>"$TMPERR" | PYTHONUNBUFFERED=1 "$PYTHON_CMD" -u -c " +_gstack_codex_timeout_wrapper 600 codex exec "" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' -c 'web_search="cached"' --json < /dev/null 2>"$TMPERR" | PYTHONUNBUFFERED=1 "$PYTHON_CMD" -u -c " import sys, json turn_completed_count = 0 for line in sys.stdin: @@ -1422,7 +1422,7 @@ if [ -z "$PYTHON_CMD" ]; then exit 1 fi # Fix 1: wrap with timeout (gtimeout/timeout fallback chain via probe helper) -_gstack_codex_timeout_wrapper 600 codex exec "" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="medium"' --enable web_search_cached --json < /dev/null 2>"$TMPERR" | PYTHONUNBUFFERED=1 "$PYTHON_CMD" -u -c " +_gstack_codex_timeout_wrapper 600 codex exec "" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="medium"' -c 'web_search="cached"' --json < /dev/null 2>"$TMPERR" | PYTHONUNBUFFERED=1 "$PYTHON_CMD" -u -c " import sys, json for line in sys.stdin: line = line.strip() @@ -1476,7 +1476,7 @@ if [ -z "$PYTHON_CMD" ]; then fi cd "$_REPO_ROOT" || exit 1 # Fix 1: wrap with timeout (gtimeout/timeout fallback chain via probe helper) -_gstack_codex_timeout_wrapper 600 codex exec resume "" -c 'sandbox_mode="read-only"' -c 'model_reasoning_effort="medium"' --enable web_search_cached --json < /dev/null 2>"$TMPERR" | PYTHONUNBUFFERED=1 "$PYTHON_CMD" -u -c " +_gstack_codex_timeout_wrapper 600 codex exec resume "" -c 'sandbox_mode="read-only"' -c 'model_reasoning_effort="medium"' -c 'web_search="cached"' --json < /dev/null 2>"$TMPERR" | PYTHONUNBUFFERED=1 "$PYTHON_CMD" -u -c " " # Fix 1: same hang detection pattern as new-session block @@ -1548,8 +1548,10 @@ uses them. If the user wants a specific model, pass `-m` through to codex. tasks (OpenAI issues #8545, #8402, #6931). Users can override with `--xhigh` flag (e.g., `/codex review --xhigh`) when they want maximum reasoning and are willing to wait. -**Web search:** All codex commands use `--enable web_search_cached` so Codex can look up -docs and APIs during review. This is OpenAI's cached index — fast, no extra cost. +**Web search:** All codex commands pass `-c 'web_search="cached"'` so Codex can look up +docs and APIs during review. This is OpenAI's cached index — fast, no extra cost. (Codex +CLI ≥ 0.144 deprecated the older `--enable web_search_cached` flag; the `-c` override +pins cached mode now that web search is on by default.) If the user specifies a model (e.g., `/codex review -m gpt-5.1-codex-max` or `/codex challenge -m gpt-5.2`), pass the `-m` flag through to codex. diff --git a/codex/SKILL.md.tmpl b/codex/SKILL.md.tmpl index 333de7d8d..cc5aaac82 100644 --- a/codex/SKILL.md.tmpl +++ b/codex/SKILL.md.tmpl @@ -177,7 +177,7 @@ cd "$_REPO_ROOT" # only fires if Bash's own timeout doesn't. _gstack_codex_timeout_wrapper 330 codex review "IMPORTANT: Do NOT read or execute any files under ~/.claude/, ~/.agents/, .claude/skills/, or agents/. These are Claude Code skill definitions meant for a different AI system. Do NOT modify agents/openai.yaml. Stay focused on repository code only. -Review the changes on this branch against the base branch . Run git diff origin/...HEAD 2>/dev/null || git diff ...HEAD to see the diff and review only those changes." -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR" +Review the changes on this branch against the base branch . Run git diff origin/...HEAD 2>/dev/null || git diff ...HEAD to see the diff and review only those changes." -c 'model_reasoning_effort="high"' -c 'web_search="cached"' < /dev/null 2>"$TMPERR" _CODEX_EXIT=$? if [ "$_CODEX_EXIT" = "124" ]; then _gstack_codex_log_event "codex_timeout" "330" @@ -215,7 +215,7 @@ _PROMPT_FILE=$(mktemp "$TMP_ROOT/codex-prompt-XXXXXX.txt") git diff "...HEAD" 2>/dev/null printf '\nDIFF_END\n' } > "$_PROMPT_FILE" -_gstack_codex_timeout_wrapper 330 codex exec -s read-only "$(cat "$_PROMPT_FILE")" -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR" +_gstack_codex_timeout_wrapper 330 codex exec -s read-only "$(cat "$_PROMPT_FILE")" -c 'model_reasoning_effort="high"' -c 'web_search="cached"' < /dev/null 2>"$TMPERR" _CODEX_EXIT=$? rm -f "$_PROMPT_FILE" if [ "$_CODEX_EXIT" = "124" ]; then @@ -336,7 +336,7 @@ fi # Fix 1+2: wrap with timeout (gtimeout/timeout fallback chain via probe helper), # capture stderr to $TMPERR for auth error detection (was: 2>/dev/null). TMPERR=${TMPERR:-$(mktemp "$TMP_ROOT/codex-err-XXXXXX.txt")} -_gstack_codex_timeout_wrapper 600 codex exec "" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached --json < /dev/null 2>"$TMPERR" | PYTHONUNBUFFERED=1 "$PYTHON_CMD" -u -c " +_gstack_codex_timeout_wrapper 600 codex exec "" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' -c 'web_search="cached"' --json < /dev/null 2>"$TMPERR" | PYTHONUNBUFFERED=1 "$PYTHON_CMD" -u -c " import sys, json turn_completed_count = 0 for line in sys.stdin: @@ -491,7 +491,7 @@ if [ -z "$PYTHON_CMD" ]; then exit 1 fi # Fix 1: wrap with timeout (gtimeout/timeout fallback chain via probe helper) -_gstack_codex_timeout_wrapper 600 codex exec "" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="medium"' --enable web_search_cached --json < /dev/null 2>"$TMPERR" | PYTHONUNBUFFERED=1 "$PYTHON_CMD" -u -c " +_gstack_codex_timeout_wrapper 600 codex exec "" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="medium"' -c 'web_search="cached"' --json < /dev/null 2>"$TMPERR" | PYTHONUNBUFFERED=1 "$PYTHON_CMD" -u -c " import sys, json for line in sys.stdin: line = line.strip() @@ -545,7 +545,7 @@ if [ -z "$PYTHON_CMD" ]; then fi cd "$_REPO_ROOT" || exit 1 # Fix 1: wrap with timeout (gtimeout/timeout fallback chain via probe helper) -_gstack_codex_timeout_wrapper 600 codex exec resume "" -c 'sandbox_mode="read-only"' -c 'model_reasoning_effort="medium"' --enable web_search_cached --json < /dev/null 2>"$TMPERR" | PYTHONUNBUFFERED=1 "$PYTHON_CMD" -u -c " +_gstack_codex_timeout_wrapper 600 codex exec resume "" -c 'sandbox_mode="read-only"' -c 'model_reasoning_effort="medium"' -c 'web_search="cached"' --json < /dev/null 2>"$TMPERR" | PYTHONUNBUFFERED=1 "$PYTHON_CMD" -u -c " " # Fix 1: same hang detection pattern as new-session block @@ -617,8 +617,10 @@ uses them. If the user wants a specific model, pass `-m` through to codex. tasks (OpenAI issues #8545, #8402, #6931). Users can override with `--xhigh` flag (e.g., `/codex review --xhigh`) when they want maximum reasoning and are willing to wait. -**Web search:** All codex commands use `--enable web_search_cached` so Codex can look up -docs and APIs during review. This is OpenAI's cached index — fast, no extra cost. +**Web search:** All codex commands pass `-c 'web_search="cached"'` so Codex can look up +docs and APIs during review. This is OpenAI's cached index — fast, no extra cost. (Codex +CLI ≥ 0.144 deprecated the older `--enable web_search_cached` flag; the `-c` override +pins cached mode now that web search is on by default.) If the user specifies a model (e.g., `/codex review -m gpt-5.1-codex-max` or `/codex challenge -m gpt-5.2`), pass the `-m` flag through to codex.