From 974fbf2e1539bd25dbc99b2f016f57d158448a4a Mon Sep 17 00:00:00 2001 From: smilesjosh007 Date: Fri, 17 Jul 2026 19:20:31 -0400 Subject: [PATCH 1/2] fix(codex): add shell-watchdog fallback to timeout wrapper When neither gtimeout (Homebrew coreutils) nor timeout is on PATH, _gstack_codex_timeout_wrapper ran codex completely unwrapped, so every /codex and /autoplan invocation had silent zero hang protection on a bare macOS install. setup's coreutils auto-install covers most machines but not ones where setup never ran or brew install failed. The wrapper now falls back to a pure-shell watchdog: background the command, poll every 5s, TERM at the deadline, KILL 10s later, and report exit 124 like GNU timeout so the skills' existing hang handling (_CODEX_EXIT = 124 checks, including the PIPESTATUS callers) fires unchanged. Bash "Terminated" job notices are silenced inside a stderr-swapped brace group while fd 3 carries the command's real stderr through, so call-site 2>"$TMPERR" captures keep working and no notice junk leaks into skill output. Both jobs are reaped inside the group so nothing outlives the call holding the caller's pipes open. gtimeout/timeout preference is unchanged; the watchdog only engages when both are absent. test/codex-hardening.test.ts passes 29/29. Co-Authored-By: Claude Fable 5 --- bin/gstack-codex-probe | 43 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) 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 -------------------------------------------------------- From a085bbc0666b86d836a2c4a835546e52b4e16b36 Mon Sep 17 00:00:00 2001 From: smilesjosh007 Date: Fri, 17 Jul 2026 19:20:32 -0400 Subject: [PATCH 2/2] fix(codex): migrate deprecated --enable web_search_cached to -c web_search codex-cli >= 0.144 prints a deprecation warning on every invocation: [features].web_search_cached is deprecated because web search is enabled by default; set web_search at the top level instead. Replace the flag with -c 'web_search="cached"' in the five /codex command templates (review, exec review, challenge, consult, consult resume) and update the Web search doc note. This follows the CLI's own deprecation guidance and pins the cached index explicitly rather than relying on the new default. Verified live on codex-cli 0.144.4: the old flag warns, the new override runs clean, web search stays enabled. Unlike the --search replacement proposed in #2249 (closed: codex exec and codex review reject --search on 0.144.4), -c is a stable top-level mechanism accepted by every subcommand. Scope: /codex skill only (SKILL.md.tmpl and generated SKILL.md edited in lockstep; the changed bash blocks pass through generation verbatim). The same flag remains in autoplan, ship, review, design-*, office-hours, plan-*-review sections and scripts/resolvers, left for the separate removal effort mentioned in #2249 or a follow-up PR. Co-Authored-By: Claude Fable 5 --- codex/SKILL.md | 16 +++++++++------- codex/SKILL.md.tmpl | 16 +++++++++------- 2 files changed, 18 insertions(+), 14 deletions(-) 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.