Skip to content

Commit a9f347a

Browse files
committed
fix(codex): inject -m gpt-5.2 on ChatGPT-account auth (garrytan#1628)
Codex CLI defaults to gpt-5.2-codex, which OpenAI's ChatGPT-account entitlement filter rejects with a 400 ("model is not supported when using Codex with a ChatGPT account"). Every skill that shells out to codex — /codex, /autoplan, /plan-eng-review, /ship, /plan-ceo-review, /plan-design-review — broke for ChatGPT-only auth users. Add _gstack_codex_account_kind (apikey | chatgpt | none) and _gstack_codex_default_model_args to bin/gstack-codex-probe. Templates expand $_CODEX_MODEL_ARGS=$(_gstack_codex_default_model_args) before every codex invocation; API-key users get nothing extra, ChatGPT users get `-m gpt-5.2`. $GSTACK_CODEX_MODEL overrides; set to "default" to opt out of injection entirely. Wired at all 9 callsites across codex/SKILL.md.tmpl (5) and autoplan/SKILL.md.tmpl (4). A static template guard test catches a future edit that drops the variable.
1 parent 5b59f72 commit a9f347a

6 files changed

Lines changed: 409 additions & 28 deletions

File tree

autoplan/SKILL.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,7 +1118,8 @@ Override: every AskUserQuestion → auto-decide using the 6 principles.
11181118
**Codex CEO voice** (via Bash):
11191119
```bash
11201120
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
1121-
_gstack_codex_timeout_wrapper 600 codex exec "IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only.
1121+
_CODEX_MODEL_ARGS=$(_gstack_codex_default_model_args)
1122+
_gstack_codex_timeout_wrapper 600 codex exec $_CODEX_MODEL_ARGS "IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only.
11221123
11231124
You are a CEO/founder advisor reviewing a development plan.
11241125
Challenge the strategic foundations: Are the premises valid or assumed? Is this the
@@ -1235,7 +1236,8 @@ Override: every AskUserQuestion → auto-decide using the 6 principles.
12351236
**Codex design voice** (via Bash):
12361237
```bash
12371238
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
1238-
_gstack_codex_timeout_wrapper 600 codex exec "IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only.
1239+
_CODEX_MODEL_ARGS=$(_gstack_codex_default_model_args)
1240+
_gstack_codex_timeout_wrapper 600 codex exec $_CODEX_MODEL_ARGS "IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only.
12391241
12401242
Read the plan file at <plan_path>. Evaluate this plan's
12411243
UI/UX design decisions.
@@ -1316,7 +1318,8 @@ Override: every AskUserQuestion → auto-decide using the 6 principles.
13161318
**Codex eng voice** (via Bash):
13171319
```bash
13181320
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
1319-
_gstack_codex_timeout_wrapper 600 codex exec "IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only.
1321+
_CODEX_MODEL_ARGS=$(_gstack_codex_default_model_args)
1322+
_gstack_codex_timeout_wrapper 600 codex exec $_CODEX_MODEL_ARGS "IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only.
13201323
13211324
Review this plan for architectural issues, missing edge cases,
13221325
and hidden complexity. Be adversarial.
@@ -1437,7 +1440,8 @@ Log: "Phase 3.5 skipped — no developer-facing scope detected."
14371440
**Codex DX voice** (via Bash):
14381441
```bash
14391442
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
1440-
_gstack_codex_timeout_wrapper 600 codex exec "IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only.
1443+
_CODEX_MODEL_ARGS=$(_gstack_codex_default_model_args)
1444+
_gstack_codex_timeout_wrapper 600 codex exec $_CODEX_MODEL_ARGS "IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only.
14411445
14421446
Read the plan file at <plan_path>. Evaluate this plan's developer experience.
14431447

autoplan/SKILL.md.tmpl

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,8 @@ Override: every AskUserQuestion → auto-decide using the 6 principles.
290290
**Codex CEO voice** (via Bash):
291291
```bash
292292
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
293-
_gstack_codex_timeout_wrapper 600 codex exec "IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only.
293+
_CODEX_MODEL_ARGS=$(_gstack_codex_default_model_args)
294+
_gstack_codex_timeout_wrapper 600 codex exec $_CODEX_MODEL_ARGS "IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only.
294295
295296
You are a CEO/founder advisor reviewing a development plan.
296297
Challenge the strategic foundations: Are the premises valid or assumed? Is this the
@@ -407,7 +408,8 @@ Override: every AskUserQuestion → auto-decide using the 6 principles.
407408
**Codex design voice** (via Bash):
408409
```bash
409410
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
410-
_gstack_codex_timeout_wrapper 600 codex exec "IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only.
411+
_CODEX_MODEL_ARGS=$(_gstack_codex_default_model_args)
412+
_gstack_codex_timeout_wrapper 600 codex exec $_CODEX_MODEL_ARGS "IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only.
411413
412414
Read the plan file at <plan_path>. Evaluate this plan's
413415
UI/UX design decisions.
@@ -488,7 +490,8 @@ Override: every AskUserQuestion → auto-decide using the 6 principles.
488490
**Codex eng voice** (via Bash):
489491
```bash
490492
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
491-
_gstack_codex_timeout_wrapper 600 codex exec "IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only.
493+
_CODEX_MODEL_ARGS=$(_gstack_codex_default_model_args)
494+
_gstack_codex_timeout_wrapper 600 codex exec $_CODEX_MODEL_ARGS "IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only.
492495
493496
Review this plan for architectural issues, missing edge cases,
494497
and hidden complexity. Be adversarial.
@@ -609,7 +612,8 @@ Log: "Phase 3.5 skipped — no developer-facing scope detected."
609612
**Codex DX voice** (via Bash):
610613
```bash
611614
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
612-
_gstack_codex_timeout_wrapper 600 codex exec "IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only.
615+
_CODEX_MODEL_ARGS=$(_gstack_codex_default_model_args)
616+
_gstack_codex_timeout_wrapper 600 codex exec $_CODEX_MODEL_ARGS "IMPORTANT: Do NOT read or execute any SKILL.md files or files in skill definition directories (paths containing skills/gstack). These are AI assistant skill definitions meant for a different system. Stay focused on repository code only.
613617
614618
Read the plan file at <plan_path>. Evaluate this plan's developer experience.
615619

bin/gstack-codex-probe

Lines changed: 87 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
# Sourced from template bash blocks; never execute directly.
44
#
55
# Functions (all prefixed with _gstack_codex_ for namespace hygiene):
6-
# _gstack_codex_auth_probe — multi-signal auth check (env + file)
7-
# _gstack_codex_version_check — warn on known-bad Codex CLI versions
8-
# _gstack_codex_timeout_wrapper — gtimeout -> timeout -> unwrapped fallback
9-
# _gstack_codex_log_event — telemetry emission to ~/.gstack/analytics/
6+
# _gstack_codex_auth_probe — multi-signal auth check (env + file)
7+
# _gstack_codex_account_kind — classify auth as apikey | chatgpt | none
8+
# _gstack_codex_default_model_args — emit `-m <model>` when ChatGPT auth needs
9+
# a non-default model to avoid 400s (#1628)
10+
# _gstack_codex_version_check — warn on known-bad Codex CLI versions
11+
# _gstack_codex_timeout_wrapper — gtimeout -> timeout -> unwrapped fallback
12+
# _gstack_codex_log_event — telemetry emission to ~/.gstack/analytics/
1013
#
1114
# Hygiene rules (enforced by test/codex-hardening.test.ts):
1215
# - Never set -e / set -u / trap / IFS= / PATH= in this file.
@@ -33,6 +36,86 @@ _gstack_codex_auth_probe() {
3336
return 1
3437
}
3538

39+
# --- Account kind classifier ------------------------------------------------
40+
#
41+
# Echoes one of: apikey | chatgpt | none.
42+
#
43+
# apikey → CODEX_API_KEY or OPENAI_API_KEY is set (non-empty, non-whitespace).
44+
# Codex CLI bills the OpenAI Platform key and the user is entitled
45+
# to every published model, including gpt-5.2-codex.
46+
# chatgpt → no api-key env vars, but ${CODEX_HOME:-~/.codex}/auth.json exists.
47+
# Codex CLI authenticates as the user's ChatGPT account. As of
48+
# March 2026 the ChatGPT entitlement set excludes gpt-5.2-codex
49+
# (returns "model is not supported when using Codex with a
50+
# ChatGPT account") and only includes the base gpt-5.2 family —
51+
# see #1628.
52+
# none → no auth signal at all; the caller should fail fast.
53+
54+
_gstack_codex_account_kind() {
55+
local _k1 _k2
56+
_k1=$(printf '%s' "${CODEX_API_KEY:-}" | tr -d '[:space:]')
57+
_k2=$(printf '%s' "${OPENAI_API_KEY:-}" | tr -d '[:space:]')
58+
if [ -n "$_k1" ] || [ -n "$_k2" ]; then
59+
echo "apikey"
60+
return 0
61+
fi
62+
local _codex_home="${CODEX_HOME:-$HOME/.codex}"
63+
if [ -f "$_codex_home/auth.json" ]; then
64+
echo "chatgpt"
65+
return 0
66+
fi
67+
echo "none"
68+
}
69+
70+
# --- Default model args -----------------------------------------------------
71+
#
72+
# Echoes the codex CLI flags that should be injected before the prompt /
73+
# subcommand. The two callsite patterns are:
74+
#
75+
# _CODEX_MODEL_ARGS=$(_gstack_codex_default_model_args)
76+
# _gstack_codex_timeout_wrapper 330 codex review $_CODEX_MODEL_ARGS "..." ...
77+
#
78+
# Or as a `bash -c` style array:
79+
#
80+
# read -r -a _CMA <<<"$(_gstack_codex_default_model_args)"
81+
#
82+
# Behaviour:
83+
# - $GSTACK_CODEX_MODEL set → emit `-m $GSTACK_CODEX_MODEL` regardless of
84+
# auth kind. Power-user override, e.g. for testing gpt-5.1-codex-max.
85+
# - $GSTACK_CODEX_MODEL = "" or "default" → emit nothing (let Codex pick).
86+
# - account = chatgpt → emit `-m gpt-5.2` so the Codex CLI's default
87+
# gpt-5.2-codex selection doesn't trip OpenAI's ChatGPT entitlement
88+
# filter and 400 the request (issue #1628).
89+
# - account = apikey → emit nothing; API-key users have full entitlement.
90+
# - account = none → emit nothing; let the auth probe handle the error.
91+
#
92+
# The output is a single line containing zero or two whitespace-separated
93+
# tokens, suitable for unquoted interpolation in a codex command line.
94+
95+
_gstack_codex_default_model_args() {
96+
local _override="${GSTACK_CODEX_MODEL:-}"
97+
case "$_override" in
98+
"")
99+
# Unset / empty → fall through to account-based auto-detection below.
100+
;;
101+
"default")
102+
# Power-user opt-out: bypass injection entirely so the Codex CLI picks
103+
# its own model. Useful if/when ChatGPT-account entitlement changes and
104+
# the chatgpt path stops being needed.
105+
return 0
106+
;;
107+
*)
108+
printf -- '-m %s' "$_override"
109+
return 0
110+
;;
111+
esac
112+
local _kind
113+
_kind=$(_gstack_codex_account_kind)
114+
if [ "$_kind" = "chatgpt" ]; then
115+
printf -- '-m gpt-5.2'
116+
fi
117+
}
118+
36119
# --- Version check ----------------------------------------------------------
37120

38121
_gstack_codex_version_check() {

codex/SKILL.md

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -947,7 +947,11 @@ _REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo"
947947
cd "$_REPO_ROOT"
948948
# 330s (5.5min) is slightly longer than the Bash 300s so the shell wrapper
949949
# only fires if Bash's own timeout doesn't.
950-
_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.
950+
# $_CODEX_MODEL_ARGS forces `-m gpt-5.2` on ChatGPT-account auth so Codex's
951+
# default `gpt-5.2-codex` doesn't 400 against the ChatGPT entitlement filter
952+
# (issue #1628). On API-key auth it's empty and the default model wins.
953+
_CODEX_MODEL_ARGS=$(_gstack_codex_default_model_args)
954+
_gstack_codex_timeout_wrapper 330 codex review $_CODEX_MODEL_ARGS "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.
951955
952956
Review the changes on this branch against the base branch <base>. Run git diff origin/<base>...HEAD 2>/dev/null || git diff <base>...HEAD to see the diff and review only those changes." -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR"
953957
_CODEX_EXIT=$?
@@ -987,7 +991,8 @@ _PROMPT_FILE=$(mktemp "$TMP_ROOT/codex-prompt-XXXXXX.txt")
987991
git diff "<base>...HEAD" 2>/dev/null
988992
printf '\nDIFF_END\n'
989993
} > "$_PROMPT_FILE"
990-
_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"
994+
_CODEX_MODEL_ARGS=$(_gstack_codex_default_model_args)
995+
_gstack_codex_timeout_wrapper 330 codex exec $_CODEX_MODEL_ARGS -s read-only "$(cat "$_PROMPT_FILE")" -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR"
991996
_CODEX_EXIT=$?
992997
rm -f "$_PROMPT_FILE"
993998
if [ "$_CODEX_EXIT" = "124" ]; then
@@ -1215,7 +1220,8 @@ fi
12151220
# Fix 1+2: wrap with timeout (gtimeout/timeout fallback chain via probe helper),
12161221
# capture stderr to $TMPERR for auth error detection (was: 2>/dev/null).
12171222
TMPERR=${TMPERR:-$(mktemp "$TMP_ROOT/codex-err-XXXXXX.txt")}
1218-
_gstack_codex_timeout_wrapper 600 codex exec "<prompt>" -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 "
1223+
_CODEX_MODEL_ARGS=$(_gstack_codex_default_model_args)
1224+
_gstack_codex_timeout_wrapper 600 codex exec $_CODEX_MODEL_ARGS "<prompt>" -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 "
12191225
import sys, json
12201226
turn_completed_count = 0
12211227
for line in sys.stdin:
@@ -1370,7 +1376,8 @@ if [ -z "$PYTHON_CMD" ]; then
13701376
exit 1
13711377
fi
13721378
# Fix 1: wrap with timeout (gtimeout/timeout fallback chain via probe helper)
1373-
_gstack_codex_timeout_wrapper 600 codex exec "<prompt>" -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 "
1379+
_CODEX_MODEL_ARGS=$(_gstack_codex_default_model_args)
1380+
_gstack_codex_timeout_wrapper 600 codex exec $_CODEX_MODEL_ARGS "<prompt>" -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 "
13741381
import sys, json
13751382
for line in sys.stdin:
13761383
line = line.strip()
@@ -1424,7 +1431,8 @@ if [ -z "$PYTHON_CMD" ]; then
14241431
fi
14251432
cd "$_REPO_ROOT" || exit 1
14261433
# Fix 1: wrap with timeout (gtimeout/timeout fallback chain via probe helper)
1427-
_gstack_codex_timeout_wrapper 600 codex exec resume <session-id> "<prompt>" -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 "
1434+
_CODEX_MODEL_ARGS=$(_gstack_codex_default_model_args)
1435+
_gstack_codex_timeout_wrapper 600 codex exec resume <session-id> $_CODEX_MODEL_ARGS "<prompt>" -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 "
14281436
<same python streaming parser as above, with flush=True on all print() calls>
14291437
"
14301438
# Fix 1: same hang detection pattern as new-session block
@@ -1483,9 +1491,16 @@ The reason must engage with a specific Codex insight and compare against an alte
14831491
14841492
## Model & Reasoning
14851493
1486-
**Model:** No model is hardcoded — codex uses whatever its current default is (the frontier
1487-
agentic coding model). This means as OpenAI ships newer models, /codex automatically
1488-
uses them. If the user wants a specific model, pass `-m` through to codex.
1494+
**Model:** Codex picks the default model unless one is required. Two cases inject `-m`:
1495+
1. **ChatGPT-account auth (no `$CODEX_API_KEY` / `$OPENAI_API_KEY`):** the
1496+
probe injects `-m gpt-5.2` so Codex's default `gpt-5.2-codex` doesn't trip
1497+
OpenAI's ChatGPT-account entitlement filter and return 400 (issue #1628).
1498+
2. **`$GSTACK_CODEX_MODEL` set:** that exact model is injected; set it to
1499+
`"default"` or unset it to let Codex decide.
1500+
1501+
API-key users hit no injection — they're entitled to every published model.
1502+
If the user passes their own `-m` in the slash command, thread it through;
1503+
the last `-m` on the codex command line wins.
14891504
14901505
**Reasoning effort (per-mode defaults):**
14911506
- **Review (2A):** `high` — bounded diff input, needs thoroughness but not max tokens

0 commit comments

Comments
 (0)