v1.91.45.0 feat: configurable model tiers and advisory freshness (#3073)

* feat: add configurable model tiers and advisory freshness

* fix: disclose role selections before Claude review execution

* fix: reject malformed model policies and keep model pins portable

* fix: separate readiness diagnostics and bound version checks

* fix: preserve actionable model failures and validate review inputs before probes

* fix: retry released probe locks and retain late-rejection repair context

* feat: finalize configurable model tiers and verified release repairs

* fix: preserve strict model policy reads on Windows

* fix: keep fixture probes read-only and clarify pending CEO decisions

* fix: verify native terminal closure before bounded descriptor settlement
This commit is contained in:
Garry Tan authored and GitHub committed 2026-10-08 08:00:08 +00:00
1 parent 52eec18c63
commit 92cfd07a79
96 files changed
+6539 -411

No files matched your search

+14 -9
View File
@@ -470,12 +470,16 @@ and every outside review run as a command, one subcommand per check.
Model order: a model the user names for this request, `GSTACK_CODEX_MODEL`, Codex
`config.toml` `model` (`review_model` first for `codex review`; honors `$CODEX_HOME`),
then `gpt-6-astra`. Each call prints `CODEX_MODEL: <model> (<kind>; source: ...)` first.
For a named model, add `--model '<model>'` to every `select-model` and `probe-model`
call, including the probe below. An invalid or unavailable choice stops with a repair
message, never the default.
For a named model, add `--model '<model>'` to every `select-model`, `probe-model` and
`role-ready` call, including the probe below. An invalid or unavailable choice stops
with a repair message, never the default; a named model also wins over a role.
Only for an explicit `--role plan-review` (e.g. `/codex challenge --role plan-review`),
set `_CODEX_ROLE='plan-review'` below and in the mode block; the mode block then
probes and dispatches the policy model within its own timeout. [Policy setup](https://github.com/garrytan/gstack/blob/main/docs/model-policy.md).
```bash
_CODEX_PROBE=~/.claude/skills/gstack/bin/gstack-codex-probe
_CODEX_ROLE=''
[ -x "$_CODEX_PROBE" ] || { echo "HELPER_UNAVAILABLE"; exit 1; }
# GSTACK_ACTIVE_HOST names the harness, never the model.
@@ -491,7 +495,7 @@ fi
if ! "$_CODEX_PROBE" check-auth >/dev/null; then
"$_CODEX_PROBE" log-event codex_auth_failed
echo "AUTH_FAILED"
elif "$_CODEX_PROBE" check-sandbox; then # free; Linux only
elif "$_CODEX_PROBE" check-sandbox && [ -z "$_CODEX_ROLE" ]; then # free; Linux only
"$_CODEX_PROBE" probe-model exec # ~10s round trip on first run, cached 1h
fi
"$_CODEX_PROBE" check-version # warns if known-bad, non-blocking
@@ -847,11 +851,12 @@ If token count is not available, display: `Tokens: unknown`
None of these is an auth or network failure, and the auth probe cannot catch them.
Recovery, in order:
1. Read the `CODEX_MODEL:` line: it names the model and where it came from.
2. Fix that source: name another model for this request, update
`GSTACK_CODEX_MODEL`, or change `model` (or `review_model`) in the Codex
`config.toml`. With none of these set, gstack uses `gpt-6-astra`; set any of
them to a model the account can use.
3. If Codex printed `[notice.model_migrations]`, use that replacement model.
2. Fix that source and relay its HINT/Repair lines. With `--role plan-review`,
tier pins outrank native settings; native `model`/`review_model` applies
only in `host` mode. Without a role, request, `GSTACK_CODEX_MODEL`, then
native settings choose the model, falling back to `gpt-6-astra`.
3. If Codex printed `[notice.model_migrations]`, use that replacement for
no-role calls. For a role, offer an explicit override; do not silently substitute.
Never present this as a model stall or a PASS — it is a fail-closed gate result.
- **`VERDICT: unavailable`:** the shared validator found the run did not execute (for
example `Codex's sandbox could not start here`). Relay its line and fix verbatim; it is
+14 -9
View File
@@ -70,19 +70,23 @@ and every outside review run as a command, one subcommand per check.
Model order: a model the user names for this request, `GSTACK_CODEX_MODEL`, Codex
`config.toml` `model` (`review_model` first for `codex review`; honors `$CODEX_HOME`),
then `gpt-6-astra`. Each call prints `CODEX_MODEL: <model> (<kind>; source: ...)` first.
For a named model, add `--model '<model>'` to every `select-model` and `probe-model`
call, including the probe below. An invalid or unavailable choice stops with a repair
message, never the default.
For a named model, add `--model '<model>'` to every `select-model`, `probe-model` and
`role-ready` call, including the probe below. An invalid or unavailable choice stops
with a repair message, never the default; a named model also wins over a role.
Only for an explicit `--role plan-review` (e.g. `/codex challenge --role plan-review`),
set `_CODEX_ROLE='plan-review'` below and in the mode block; the mode block then
probes and dispatches the policy model within its own timeout. [Policy setup](https://github.com/garrytan/gstack/blob/main/docs/model-policy.md).
```bash
_CODEX_PROBE=~/.claude/skills/gstack/bin/gstack-codex-probe
_CODEX_ROLE=''
[ -x "$_CODEX_PROBE" ] || { echo "HELPER_UNAVAILABLE"; exit 1; }
{{OUTSIDE_SELF_GUARD:codex}}
if ! "$_CODEX_PROBE" check-auth >/dev/null; then
"$_CODEX_PROBE" log-event codex_auth_failed
echo "AUTH_FAILED"
elif "$_CODEX_PROBE" check-sandbox; then # free; Linux only
elif "$_CODEX_PROBE" check-sandbox && [ -z "$_CODEX_ROLE" ]; then # free; Linux only
"$_CODEX_PROBE" probe-model exec # ~10s round trip on first run, cached 1h
fi
"$_CODEX_PROBE" check-version # warns if known-bad, non-blocking
@@ -313,11 +317,12 @@ If token count is not available, display: `Tokens: unknown`
None of these is an auth or network failure, and the auth probe cannot catch them.
Recovery, in order:
1. Read the `CODEX_MODEL:` line: it names the model and where it came from.
2. Fix that source: name another model for this request, update
`GSTACK_CODEX_MODEL`, or change `model` (or `review_model`) in the Codex
`config.toml`. With none of these set, gstack uses `gpt-6-astra`; set any of
them to a model the account can use.
3. If Codex printed `[notice.model_migrations]`, use that replacement model.
2. Fix that source and relay its HINT/Repair lines. With `--role plan-review`,
tier pins outrank native settings; native `model`/`review_model` applies
only in `host` mode. Without a role, request, `GSTACK_CODEX_MODEL`, then
native settings choose the model, falling back to `gpt-6-astra`.
3. If Codex printed `[notice.model_migrations]`, use that replacement for
no-role calls. For a role, offer an explicit override; do not silently substitute.
Never present this as a model stall or a PASS — it is a fail-closed gate result.
- **`VERDICT: unavailable`:** the shared validator found the run did not execute (for
example `Codex's sandbox could not start here`). Relay its line and fix verbatim; it is
+5 -3
View File
@@ -39,6 +39,8 @@ If the user passed `--xhigh`, use `"xhigh"` instead of `"high"`.
```bash
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
PROMPT_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/<prompt-file-name>"
[ -s "$PROMPT_FILE" ] || { echo "Not run: $PROMPT_FILE is missing or empty, so the prompt was never written. Write it, then run by hand: codex exec - -C $_REPO_ROOT < $PROMPT_FILE" >&2; exit 1; }
PYTHON_CMD=$(command -v python3 2>/dev/null || command -v python 2>/dev/null || true)
if [ -z "$PYTHON_CMD" ]; then
echo "ERROR: Python 3 is required to parse Codex JSON output. Install python3 or python and retry." >&2
@@ -49,11 +51,11 @@ fi
[ -n "${TMPERR:-}" ] || TMPERR=$(mktemp "$TMP_ROOT/codex-err-XXXXXX") || { echo "ERROR: mktemp failed in TMP_ROOT=$TMP_ROOT; not running codex without its temp file" >&2; exit 1; }
TMPRESP=$(mktemp "$TMP_ROOT/codex-resp-XXXXXX") || { echo "ERROR: mktemp failed in TMP_ROOT=$TMP_ROOT; not running codex without its temp file" >&2; exit 1; }
_CODEX_PROBE=~/.claude/skills/gstack/bin/gstack-codex-probe
_CODEX_OUT=$("$_CODEX_PROBE" select-model exec) || exit 1
_CODEX_ROLE=''
if [ -n "$_CODEX_ROLE" ]; then export _CODEX_DEADLINE=$(($(date +%s)+540)); _CODEX_OUT=$("$_CODEX_PROBE" role-ready exec) || exit $?
else _CODEX_OUT=$("$_CODEX_PROBE" select-model exec) || exit 1; fi
_CODEX_SEL=$(echo "$_CODEX_OUT" | sed -n 's/^CODEX_SEL: //p')
_CODEX_SANDBOX_MODE=$(echo "$_CODEX_OUT" | sed -n 's/^CODEX_SANDBOX: //p')
PROMPT_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/<prompt-file-name>"
[ -s "$PROMPT_FILE" ] || { echo "Not run: $PROMPT_FILE is missing or empty, so the prompt was never written. Write it, then run by hand: codex exec - -C $_REPO_ROOT < $PROMPT_FILE" >&2; exit 1; }
"$_CODEX_PROBE" run-with-timeout 540 codex exec - -C "$_REPO_ROOT" -s "${_CODEX_SANDBOX_MODE:?}" -c "model=\"${_CODEX_SEL:?}\"" -c skills.include_instructions=false -c 'model_reasoning_effort="high"' -c 'web_search="cached"' --json -o "$TMPRESP" < "$PROMPT_FILE" 2>"$TMPERR" | tee "$TMPRESP.events" | PYTHONUNBUFFERED=1 "$PYTHON_CMD" -u -c "
import sys, json
turn_completed_count = 0
+3 -3
View File
@@ -30,6 +30,8 @@ If the user passed `--xhigh`, use `"xhigh"` instead of `"high"`.
```bash
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
PROMPT_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/<prompt-file-name>"
[ -s "$PROMPT_FILE" ] || { echo "Not run: $PROMPT_FILE is missing or empty, so the prompt was never written. Write it, then run by hand: codex exec - -C $_REPO_ROOT < $PROMPT_FILE" >&2; exit 1; }
PYTHON_CMD=$(command -v python3 2>/dev/null || command -v python 2>/dev/null || true)
if [ -z "$PYTHON_CMD" ]; then
echo "ERROR: Python 3 is required to parse Codex JSON output. Install python3 or python and retry." >&2
@@ -39,9 +41,7 @@ fi
# capture stderr to $TMPERR for auth error detection (was: 2>/dev/null).
[ -n "${TMPERR:-}" ] || TMPERR=$(mktemp "$TMP_ROOT/codex-err-XXXXXX") || { echo "ERROR: mktemp failed in TMP_ROOT=$TMP_ROOT; not running codex without its temp file" >&2; exit 1; }
TMPRESP=$(mktemp "$TMP_ROOT/codex-resp-XXXXXX") || { echo "ERROR: mktemp failed in TMP_ROOT=$TMP_ROOT; not running codex without its temp file" >&2; exit 1; }
{{CODEX_SELECT:exec}}
PROMPT_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/<prompt-file-name>"
[ -s "$PROMPT_FILE" ] || { echo "Not run: $PROMPT_FILE is missing or empty, so the prompt was never written. Write it, then run by hand: codex exec - -C $_REPO_ROOT < $PROMPT_FILE" >&2; exit 1; }
{{CODEX_SELECT:exec:540}}
"$_CODEX_PROBE" run-with-timeout 540 codex exec - -C "$_REPO_ROOT" -s "${_CODEX_SANDBOX_MODE:?}" {{CODEX_MODEL_CONFIG_FLAG}} -c 'model_reasoning_effort="high"' {{CODEX_WEB_SEARCH_FLAG}} --json -o "$TMPRESP" < "$PROMPT_FILE" 2>"$TMPERR" | tee "$TMPRESP.events" | PYTHONUNBUFFERED=1 "$PYTHON_CMD" -u -c "
import sys, json
turn_completed_count = 0
+3 -1
View File
@@ -102,7 +102,9 @@ cd "$_REPO_ROOT" || exit 1
PROMPT_FILE="$_REPO_ROOT/.gstack/tmp/<prompt-file-name>"
[ -s "$PROMPT_FILE" ] || { echo "Not run: $PROMPT_FILE is missing or empty, so the prompt was never written. Write it, then run by hand: codex exec - -C $_REPO_ROOT < $PROMPT_FILE" >&2; exit 1; }
_CODEX_PROBE=~/.claude/skills/gstack/bin/gstack-codex-probe
_CODEX_OUT=$("$_CODEX_PROBE" select-model exec) || exit 1
_CODEX_ROLE=''
if [ -n "$_CODEX_ROLE" ]; then export _CODEX_DEADLINE=$(($(date +%s)+540)); _CODEX_OUT=$("$_CODEX_PROBE" role-ready exec) || exit $?
else _CODEX_OUT=$("$_CODEX_PROBE" select-model exec) || exit 1; fi
_CODEX_SEL=$(echo "$_CODEX_OUT" | sed -n 's/^CODEX_SEL: //p')
_CODEX_SANDBOX_MODE=$(echo "$_CODEX_OUT" | sed -n 's/^CODEX_SANDBOX: //p')
_SID=""
+1 -1
View File
@@ -92,7 +92,7 @@ fi
cd "$_REPO_ROOT" || exit 1
PROMPT_FILE="$_REPO_ROOT/.gstack/tmp/<prompt-file-name>"
[ -s "$PROMPT_FILE" ] || { echo "Not run: $PROMPT_FILE is missing or empty, so the prompt was never written. Write it, then run by hand: codex exec - -C $_REPO_ROOT < $PROMPT_FILE" >&2; exit 1; }
{{CODEX_SELECT:exec}}
{{CODEX_SELECT:exec:540}}
_SID=""
if [ "$_CODEX_MODE" = "resume" ]; then
_SID=$(cat .context/codex-session-id 2>/dev/null)
+6 -2
View File
@@ -48,7 +48,9 @@ _REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo"
cd "$_REPO_ROOT"
~/.claude/skills/gstack/bin/gstack-review-log --start codex-review
_CODEX_PROBE=~/.claude/skills/gstack/bin/gstack-codex-probe
_CODEX_OUT=$("$_CODEX_PROBE" select-model review) || exit 1
_CODEX_ROLE=''
if [ -n "$_CODEX_ROLE" ]; then export _CODEX_DEADLINE=$(($(date +%s)+330)); _CODEX_OUT=$("$_CODEX_PROBE" role-ready review) || exit $?
else _CODEX_OUT=$("$_CODEX_PROBE" select-model review) || exit 1; fi
_CODEX_SEL=$(echo "$_CODEX_OUT" | sed -n 's/^CODEX_SEL: //p')
_CODEX_SANDBOX_MODE=$(echo "$_CODEX_OUT" | sed -n 's/^CODEX_SANDBOX: //p')
# The 330s wrapper sits BELOW the 360s Bash gate so the wrapper fires FIRST
@@ -106,7 +108,9 @@ FOCUS_FILE="$_REPO_ROOT/.gstack/tmp/<focus-file-name>"
[ -s "$FOCUS_FILE" ] || { echo "Not run: $FOCUS_FILE is missing or empty, so the focus text was never written. Write it, then run this block again." >&2; exit 1; }
~/.claude/skills/gstack/bin/gstack-review-log --start codex-review
_CODEX_PROBE=~/.claude/skills/gstack/bin/gstack-codex-probe
_CODEX_OUT=$("$_CODEX_PROBE" select-model exec) || exit 1
_CODEX_ROLE=''
if [ -n "$_CODEX_ROLE" ]; then export _CODEX_DEADLINE=$(($(date +%s)+330)); _CODEX_OUT=$("$_CODEX_PROBE" role-ready exec) || exit $?
else _CODEX_OUT=$("$_CODEX_PROBE" select-model exec) || exit 1; fi
_CODEX_SEL=$(echo "$_CODEX_OUT" | sed -n 's/^CODEX_SEL: //p')
_CODEX_SANDBOX_MODE=$(echo "$_CODEX_OUT" | sed -n 's/^CODEX_SANDBOX: //p')
_PROMPT_FILE=$(mktemp "$TMP_ROOT/codex-prompt-XXXXXX") || { echo "ERROR: mktemp failed in TMP_ROOT=$TMP_ROOT; not running codex without its temp file" >&2; exit 1; }
+2 -2
View File
@@ -45,7 +45,7 @@ contradicting this skill's read-only contract:
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
cd "$_REPO_ROOT"
~/.claude/skills/gstack/bin/gstack-review-log --start codex-review
{{CODEX_SELECT:review}}
{{CODEX_SELECT:review:330}}
# The 330s wrapper sits BELOW the 360s Bash gate so the wrapper fires FIRST
# and a stall surfaces as a diagnosable exit 124 with an explicit message,
# never as a silent harness kill that downstream reads as "no findings".
@@ -93,7 +93,7 @@ cd "$_REPO_ROOT"
FOCUS_FILE="$_REPO_ROOT/.gstack/tmp/<focus-file-name>"
[ -s "$FOCUS_FILE" ] || { echo "Not run: $FOCUS_FILE is missing or empty, so the focus text was never written. Write it, then run this block again." >&2; exit 1; }
~/.claude/skills/gstack/bin/gstack-review-log --start codex-review
{{CODEX_SELECT:exec}}
{{CODEX_SELECT:exec:330}}
_PROMPT_FILE=$(mktemp "$TMP_ROOT/codex-prompt-XXXXXX") || { echo "ERROR: mktemp failed in TMP_ROOT=$TMP_ROOT; not running codex without its temp file" >&2; exit 1; }
{
printf '%s\n' "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 invoke any installed skill (Codex home skills/, .agents/); answer directly. Do NOT modify agents/openai.yaml. Stay focused on repository code only."