fix: gate Codex invocations by project auth profile

This commit is contained in:
Test 2026-07-15 14:37:09 -07:00
parent a3259400a3
commit 03a617da2a
20 changed files with 210 additions and 25 deletions

View File

@ -935,7 +935,9 @@ These rules auto-answer every intermediate question:
Every auto-decision is classified:
**Mechanical** — one clearly right answer. Auto-decide silently.
Examples: run codex (always yes), run evals (always yes), reduce scope on a complete plan (always no).
Examples: run evals (always yes), reduce scope on a complete plan (always no).
Running Codex is never mechanical: it requires the project-approved profile
gate below and disclosure of the selected account path.
**Taste** — reasonable people could disagree. Auto-decide with recommendation, but surface at the final gate. Three natural sources:
1. **Close approaches** — top two are both viable with different tradeoffs.
@ -1110,6 +1112,10 @@ _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || ec
_CODEX_CFG=$(~/.claude/skills/gstack/bin/gstack-config get codex_reviews 2>/dev/null || echo enabled)
source ~/.claude/skills/gstack/bin/gstack-codex-probe
if ! _gstack_codex_profile_gate; then
echo "PROFILE_REQUIRED"
fi
# Master switch first: codex_reviews=disabled turns off ALL Codex work globally,
# including autoplan's own dual-voice orchestration. Honor it before probing.
if [ "$_CODEX_CFG" = "disabled" ]; then
@ -1131,6 +1137,13 @@ else
fi
```
If `PROFILE_REQUIRED` appears, use AskUserQuestion before any Codex voice runs.
List the discovered `~/.codex*` directories and ask which profile is approved
for this project, plus Cancel. Save the selected absolute path as
`codex_home: <path>` in `~/.gstack/projects/$SLUG/codex.yaml`, rerun the
preflight, and disclose the selected path. Cancel means Claude-only voices; do
not invoke Codex. `GSTACK_CODEX_SKIP_GATE=1` is reserved for isolated evals.
If `_CODEX_AVAILABLE=false`, all Phase 1-3.5 Codex voices below degrade to
`[codex-unavailable]` in the degradation matrix. /autoplan completes with
Claude subagent only — saves token spend on Codex prompts we can't use.

View File

@ -71,7 +71,9 @@ These rules auto-answer every intermediate question:
Every auto-decision is classified:
**Mechanical** — one clearly right answer. Auto-decide silently.
Examples: run codex (always yes), run evals (always yes), reduce scope on a complete plan (always no).
Examples: run evals (always yes), reduce scope on a complete plan (always no).
Running Codex is never mechanical: it requires the project-approved profile
gate below and disclosure of the selected account path.
**Taste** — reasonable people could disagree. Auto-decide with recommendation, but surface at the final gate. Three natural sources:
1. **Close approaches** — top two are both viable with different tradeoffs.
@ -246,6 +248,10 @@ _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || ec
_CODEX_CFG=$(~/.claude/skills/gstack/bin/gstack-config get codex_reviews 2>/dev/null || echo enabled)
source ~/.claude/skills/gstack/bin/gstack-codex-probe
if ! _gstack_codex_profile_gate; then
echo "PROFILE_REQUIRED"
fi
# Master switch first: codex_reviews=disabled turns off ALL Codex work globally,
# including autoplan's own dual-voice orchestration. Honor it before probing.
if [ "$_CODEX_CFG" = "disabled" ]; then
@ -267,6 +273,13 @@ else
fi
```
If `PROFILE_REQUIRED` appears, use AskUserQuestion before any Codex voice runs.
List the discovered `~/.codex*` directories and ask which profile is approved
for this project, plus Cancel. Save the selected absolute path as
`codex_home: <path>` in `~/.gstack/projects/$SLUG/codex.yaml`, rerun the
preflight, and disclose the selected path. Cancel means Claude-only voices; do
not invoke Codex. `GSTACK_CODEX_SKIP_GATE=1` is reserved for isolated evals.
If `_CODEX_AVAILABLE=false`, all Phase 1-3.5 Codex voices below degrade to
`[codex-unavailable]` in the degradation matrix. /autoplan completes with
Claude subagent only — saves token spend on Codex prompts we can't use.

View File

@ -14,6 +14,41 @@
# - All functions prefix with _gstack_codex_.
# - No command execution at source time (only function defs).
# --- Per-project profile gate ----------------------------------------------
_gstack_codex_profile_gate() {
[ "${GSTACK_CODEX_SKIP_GATE:-}" = "1" ] && return 0
local _GSTACK_CODEX_BIN_DIR _GSTACK_CODEX_SLUG_OUTPUT _GSTACK_CODEX_SLUG
local _GSTACK_CODEX_STATE_ROOT _GSTACK_CODEX_CONFIG _GSTACK_CODEX_HOME
_GSTACK_CODEX_BIN_DIR=$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)
_GSTACK_CODEX_SLUG_OUTPUT=$("$_GSTACK_CODEX_BIN_DIR/gstack-slug" 2>/dev/null || true)
_GSTACK_CODEX_SLUG=$(printf '%s\n' "$_GSTACK_CODEX_SLUG_OUTPUT" | sed -n 's/^SLUG=//p' | head -1)
[ -z "$_GSTACK_CODEX_SLUG" ] && _GSTACK_CODEX_SLUG=$(basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)" | tr -cs '[:alnum:]._' '-')
_GSTACK_CODEX_STATE_ROOT=${GSTACK_HOME:-$HOME/.gstack}
_GSTACK_CODEX_CONFIG="$_GSTACK_CODEX_STATE_ROOT/projects/$_GSTACK_CODEX_SLUG/codex.yaml"
if [ ! -f "$_GSTACK_CODEX_CONFIG" ]; then
echo "CODEX_PROFILE_REQUIRED: approve a Codex home for project $_GSTACK_CODEX_SLUG before Codex runs"
find "$HOME" -maxdepth 1 -type d -name '.codex*' -print 2>/dev/null | sort
echo "Save the selection as: $_GSTACK_CODEX_CONFIG"
return 78
fi
_GSTACK_CODEX_HOME=$(sed -n 's/^[[:space:]]*codex_home:[[:space:]]*//p' "$_GSTACK_CODEX_CONFIG" | head -1)
_GSTACK_CODEX_HOME=${_GSTACK_CODEX_HOME#\"}; _GSTACK_CODEX_HOME=${_GSTACK_CODEX_HOME%\"}
_GSTACK_CODEX_HOME=${_GSTACK_CODEX_HOME#\'}; _GSTACK_CODEX_HOME=${_GSTACK_CODEX_HOME%\'}
case "$_GSTACK_CODEX_HOME" in
'~/'*) _GSTACK_CODEX_HOME="$HOME/${_GSTACK_CODEX_HOME#\~/}" ;;
esac
if [ -z "$_GSTACK_CODEX_HOME" ] || [ ! -d "$_GSTACK_CODEX_HOME" ]; then
echo "CODEX_PROFILE_REQUIRED: configured Codex home is missing or invalid: $_GSTACK_CODEX_CONFIG"
return 78
fi
export CODEX_HOME="$_GSTACK_CODEX_HOME"
echo "CODEX_PROFILE: $CODEX_HOME"
}
# --- Auth probe -------------------------------------------------------------
_gstack_codex_auth_probe() {
@ -57,6 +92,9 @@ _gstack_codex_timeout_wrapper() {
# duration in seconds; rest is the command to run.
local _duration="$1"
shift
if [ "${1##*/}" = "codex" ]; then
_gstack_codex_profile_gate || return $?
fi
local _to
_to=$(command -v gtimeout 2>/dev/null || command -v timeout 2>/dev/null || echo "")
if [ -n "$_to" ]; then

View File

@ -876,6 +876,10 @@ shared helpers that both `/codex` and `/autoplan` use.
_TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || echo off)
source ~/.claude/skills/gstack/bin/gstack-codex-probe
if ! _gstack_codex_profile_gate; then
echo "PROFILE_REQUIRED"
fi
if ! _gstack_codex_auth_probe >/dev/null; then
_gstack_codex_log_event "codex_auth_failed"
echo "AUTH_FAILED"
@ -883,6 +887,18 @@ fi
_gstack_codex_version_check # warns if known-bad, non-blocking
```
If the output contains `PROFILE_REQUIRED`, use AskUserQuestion before doing any
Codex work. List the discovered `~/.codex*` directories and ask which Codex
profile is approved for this project, plus Cancel. On selection, resolve `SLUG`
with `gstack-slug`, create `~/.gstack/projects/$SLUG/`, and write `codex.yaml`
containing `codex_home: <selected absolute path>`. Then rerun this step and tell
the user `Codex will use <path> for this project.` If they cancel, stop without
invoking Codex. Never infer a choice from whichever profile was used most
recently.
`GSTACK_CODEX_SKIP_GATE=1` is only for isolated eval harnesses; normal skill
runs must not set it.
If the output contains `AUTH_FAILED`, stop and tell the user:
"No Codex authentication found. Run `codex login` or set `$CODEX_API_KEY` / `$OPENAI_API_KEY`, then re-run this skill."

View File

@ -67,6 +67,10 @@ shared helpers that both `/codex` and `/autoplan` use.
_TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || echo off)
source ~/.claude/skills/gstack/bin/gstack-codex-probe
if ! _gstack_codex_profile_gate; then
echo "PROFILE_REQUIRED"
fi
if ! _gstack_codex_auth_probe >/dev/null; then
_gstack_codex_log_event "codex_auth_failed"
echo "AUTH_FAILED"
@ -74,6 +78,18 @@ fi
_gstack_codex_version_check # warns if known-bad, non-blocking
```
If the output contains `PROFILE_REQUIRED`, use AskUserQuestion before doing any
Codex work. List the discovered `~/.codex*` directories and ask which Codex
profile is approved for this project, plus Cancel. On selection, resolve `SLUG`
with `gstack-slug`, create `~/.gstack/projects/$SLUG/`, and write `codex.yaml`
containing `codex_home: <selected absolute path>`. Then rerun this step and tell
the user `Codex will use <path> for this project.` If they cancel, stop without
invoking Codex. Never infer a choice from whichever profile was used most
recently.
`GSTACK_CODEX_SKIP_GATE=1` is only for isolated eval harnesses; normal skill
runs must not set it.
If the output contains `AUTH_FAILED`, stop and tell the user:
"No Codex authentication found. Run `codex login` or set `$CODEX_API_KEY` / `$OPENAI_API_KEY`, then re-run this skill."

View File

@ -1144,7 +1144,7 @@ command -v codex >/dev/null 2>&1 && echo "CODEX_AVAILABLE" || echo "CODEX_NOT_AV
```bash
TMPERR_DESIGN=$(mktemp /tmp/codex-design-XXXXXXXX)
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
codex exec "Given this product context, propose a complete design direction:
source ~/.claude/skills/gstack/bin/gstack-codex-probe && _gstack_codex_timeout_wrapper 600 codex exec "Given this product context, propose a complete design direction:
- Visual thesis: one sentence describing mood, material, and energy
- Typography: specific font names (not defaults — no Inter/Roboto/Arial/system) + hex colors
- Color system: CSS variables for background, surface, primary text, muted text, accent

View File

@ -1731,7 +1731,7 @@ command -v codex >/dev/null 2>&1 && echo "CODEX_AVAILABLE" || echo "CODEX_NOT_AV
```bash
TMPERR_DESIGN=$(mktemp /tmp/codex-design-XXXXXXXX)
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
codex exec "Review the frontend source code in this repo. Evaluate against these design hard rules:
source ~/.claude/skills/gstack/bin/gstack-codex-probe && _gstack_codex_timeout_wrapper 600 codex exec "Review the frontend source code in this repo. Evaluate against these design hard rules:
- Spacing: systematic (design tokens / CSS variables) or magic numbers?
- Typography: expressive purposeful fonts or default stacks?
- Color: CSS variables with defined system, or hardcoded hex scattered?

View File

@ -379,6 +379,8 @@ if [ "$_CODEX_CFG" = "disabled" ]; then
_CODEX_MODE="disabled"
elif ! command -v codex >/dev/null 2>&1; then
_CODEX_MODE="not_installed"; _gstack_codex_log_event "codex_cli_missing" 2>/dev/null || true
elif ! _gstack_codex_profile_gate; then
_CODEX_MODE="profile_required"
elif ! _gstack_codex_auth_probe >/dev/null 2>&1; then
_CODEX_MODE="not_authed"; _gstack_codex_log_event "codex_auth_failed" 2>/dev/null || true
else
@ -390,6 +392,7 @@ echo "CODEX_MODE: $_CODEX_MODE"
Branch on the echoed `CODEX_MODE`:
- **`disabled`** — the user turned Codex reviews off (`codex_reviews=disabled`). Skip this section entirely; do NOT fall back to a Claude subagent — disabled means no extra review step. Print: "Codex review skipped (codex_reviews disabled). Re-enable: `gstack-config set codex_reviews enabled`."
- **`not_installed`** — Codex CLI absent. Print: "Codex not installed — using Claude subagent. Install for cross-model coverage: `npm install -g @openai/codex`." Fall back to the Claude subagent path.
- **`profile_required`** — Before invoking Codex, use AskUserQuestion to list the discovered `~/.codex*` directories and ask which profile is approved for this project, plus Cancel. Save the selected absolute path as `codex_home: <path>` in `~/.gstack/projects/$SLUG/codex.yaml`, then rerun this preflight and disclose the path. Cancel skips Codex. Never silently choose the last-used profile.
- **`not_authed`** — installed but no credentials. Print: "Codex installed but not authenticated — using Claude subagent. Run `codex login` or set `$CODEX_API_KEY`." Fall back to the Claude subagent path.
- **`ready`** — run the Codex pass below.
@ -428,7 +431,7 @@ THE DOCS AND DIFF: <list the touched doc paths>"
```bash
TMPERR_DOC=$(mktemp /tmp/codex-docreview-XXXXXXXX)
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
codex exec "<prompt>" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR_DOC"
source ~/.claude/skills/gstack/bin/gstack-codex-probe && _gstack_codex_timeout_wrapper 600 codex exec "<prompt>" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR_DOC"
```
Use a 5-minute timeout (`timeout: 300000`). After the command completes, read stderr:

View File

@ -1345,7 +1345,7 @@ Then add the context block and mode-appropriate instructions:
```bash
TMPERR_OH=$(mktemp /tmp/codex-oh-err-XXXXXXXX)
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
codex exec "$(cat "$CODEX_PROMPT_FILE")" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR_OH"
source ~/.claude/skills/gstack/bin/gstack-codex-probe && _gstack_codex_timeout_wrapper 600 codex exec "$(cat "$CODEX_PROMPT_FILE")" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR_OH"
```
Use a 5-minute timeout (`timeout: 300000`). After the command completes, read stderr:
@ -1595,7 +1595,7 @@ If user chooses A, launch both voices simultaneously:
```bash
TMPERR_SKETCH=$(mktemp /tmp/codex-sketch-XXXXXXXX)
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
codex exec "For this product approach, provide: a visual thesis (one sentence — mood, material, energy), a content plan (hero → support → detail → CTA), and 2 interaction ideas that change page feel. Apply beautiful defaults: composition-first, brand-first, cardless, poster not document. Be opinionated." -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="medium"' --enable web_search_cached < /dev/null 2>"$TMPERR_SKETCH"
source ~/.claude/skills/gstack/bin/gstack-codex-probe && _gstack_codex_timeout_wrapper 600 codex exec "For this product approach, provide: a visual thesis (one sentence — mood, material, energy), a content plan (hero → support → detail → CTA), and 2 interaction ideas that change page feel. Apply beautiful defaults: composition-first, brand-first, cardless, poster not document. Be opinionated." -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="medium"' --enable web_search_cached < /dev/null 2>"$TMPERR_SKETCH"
```
Use a 5-minute timeout (`timeout: 300000`). After completion: `cat "$TMPERR_SKETCH" && rm -f "$TMPERR_SKETCH"`

View File

@ -272,6 +272,8 @@ if [ "$_CODEX_CFG" = "disabled" ]; then
_CODEX_MODE="disabled"
elif ! command -v codex >/dev/null 2>&1; then
_CODEX_MODE="not_installed"; _gstack_codex_log_event "codex_cli_missing" 2>/dev/null || true
elif ! _gstack_codex_profile_gate; then
_CODEX_MODE="profile_required"
elif ! _gstack_codex_auth_probe >/dev/null 2>&1; then
_CODEX_MODE="not_authed"; _gstack_codex_log_event "codex_auth_failed" 2>/dev/null || true
else
@ -283,6 +285,7 @@ echo "CODEX_MODE: $_CODEX_MODE"
Branch on the echoed `CODEX_MODE`:
- **`disabled`** — the user turned Codex reviews off (`codex_reviews=disabled`). Skip this section entirely; do NOT fall back to a Claude subagent — disabled means no extra review step. Print: "Codex review skipped (codex_reviews disabled). Re-enable: `gstack-config set codex_reviews enabled`."
- **`not_installed`** — Codex CLI absent. Print: "Codex not installed — using Claude subagent. Install for cross-model coverage: `npm install -g @openai/codex`." Fall back to the Claude subagent path.
- **`profile_required`** — Before invoking Codex, use AskUserQuestion to list the discovered `~/.codex*` directories and ask which profile is approved for this project, plus Cancel. Save the selected absolute path as `codex_home: <path>` in `~/.gstack/projects/$SLUG/codex.yaml`, then rerun this preflight and disclose the path. Cancel skips Codex. Never silently choose the last-used profile.
- **`not_authed`** — installed but no credentials. Print: "Codex installed but not authenticated — using Claude subagent. Run `codex login` or set `$CODEX_API_KEY`." Fall back to the Claude subagent path.
- **`ready`** — run the Codex pass below.
@ -315,7 +318,7 @@ THE PLAN:
```bash
TMPERR_PV=$(mktemp /tmp/codex-planreview-XXXXXXXX)
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
codex exec "<prompt>" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR_PV"
source ~/.claude/skills/gstack/bin/gstack-codex-probe && _gstack_codex_timeout_wrapper 600 codex exec "<prompt>" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR_PV"
```
Use a 5-minute timeout (`timeout: 300000`). After the command completes, read stderr:

View File

@ -1360,7 +1360,7 @@ command -v codex >/dev/null 2>&1 && echo "CODEX_AVAILABLE" || echo "CODEX_NOT_AV
```bash
TMPERR_DESIGN=$(mktemp /tmp/codex-design-XXXXXXXX)
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
codex exec "Read the plan file at [plan-file-path]. Evaluate this plan's UI/UX design against these criteria.
source ~/.claude/skills/gstack/bin/gstack-codex-probe && _gstack_codex_timeout_wrapper 600 codex exec "Read the plan file at [plan-file-path]. Evaluate this plan's UI/UX design against these criteria.
HARD REJECTION — flag if ANY apply:
1. Generic SaaS card grid as first impression

View File

@ -258,6 +258,8 @@ if [ "$_CODEX_CFG" = "disabled" ]; then
_CODEX_MODE="disabled"
elif ! command -v codex >/dev/null 2>&1; then
_CODEX_MODE="not_installed"; _gstack_codex_log_event "codex_cli_missing" 2>/dev/null || true
elif ! _gstack_codex_profile_gate; then
_CODEX_MODE="profile_required"
elif ! _gstack_codex_auth_probe >/dev/null 2>&1; then
_CODEX_MODE="not_authed"; _gstack_codex_log_event "codex_auth_failed" 2>/dev/null || true
else
@ -269,6 +271,7 @@ echo "CODEX_MODE: $_CODEX_MODE"
Branch on the echoed `CODEX_MODE`:
- **`disabled`** — the user turned Codex reviews off (`codex_reviews=disabled`). Skip this section entirely; do NOT fall back to a Claude subagent — disabled means no extra review step. Print: "Codex review skipped (codex_reviews disabled). Re-enable: `gstack-config set codex_reviews enabled`."
- **`not_installed`** — Codex CLI absent. Print: "Codex not installed — using Claude subagent. Install for cross-model coverage: `npm install -g @openai/codex`." Fall back to the Claude subagent path.
- **`profile_required`** — Before invoking Codex, use AskUserQuestion to list the discovered `~/.codex*` directories and ask which profile is approved for this project, plus Cancel. Save the selected absolute path as `codex_home: <path>` in `~/.gstack/projects/$SLUG/codex.yaml`, then rerun this preflight and disclose the path. Cancel skips Codex. Never silently choose the last-used profile.
- **`not_authed`** — installed but no credentials. Print: "Codex installed but not authenticated — using Claude subagent. Run `codex login` or set `$CODEX_API_KEY`." Fall back to the Claude subagent path.
- **`ready`** — run the Codex pass below.
@ -301,7 +304,7 @@ THE PLAN:
```bash
TMPERR_PV=$(mktemp /tmp/codex-planreview-XXXXXXXX)
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
codex exec "<prompt>" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR_PV"
source ~/.claude/skills/gstack/bin/gstack-codex-probe && _gstack_codex_timeout_wrapper 600 codex exec "<prompt>" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR_PV"
```
Use a 5-minute timeout (`timeout: 300000`). After the command completes, read stderr:

View File

@ -348,6 +348,8 @@ if [ "$_CODEX_CFG" = "disabled" ]; then
_CODEX_MODE="disabled"
elif ! command -v codex >/dev/null 2>&1; then
_CODEX_MODE="not_installed"; _gstack_codex_log_event "codex_cli_missing" 2>/dev/null || true
elif ! _gstack_codex_profile_gate; then
_CODEX_MODE="profile_required"
elif ! _gstack_codex_auth_probe >/dev/null 2>&1; then
_CODEX_MODE="not_authed"; _gstack_codex_log_event "codex_auth_failed" 2>/dev/null || true
else
@ -359,6 +361,7 @@ echo "CODEX_MODE: $_CODEX_MODE"
Branch on the echoed `CODEX_MODE`:
- **`disabled`** — the user turned Codex reviews off (`codex_reviews=disabled`). Skip this section entirely; do NOT fall back to a Claude subagent — disabled means no extra review step. Print: "Codex review skipped (codex_reviews disabled). Re-enable: `gstack-config set codex_reviews enabled`."
- **`not_installed`** — Codex CLI absent. Print: "Codex not installed — using Claude subagent. Install for cross-model coverage: `npm install -g @openai/codex`." Fall back to the Claude subagent path.
- **`profile_required`** — Before invoking Codex, use AskUserQuestion to list the discovered `~/.codex*` directories and ask which profile is approved for this project, plus Cancel. Save the selected absolute path as `codex_home: <path>` in `~/.gstack/projects/$SLUG/codex.yaml`, then rerun this preflight and disclose the path. Cancel skips Codex. Never silently choose the last-used profile.
- **`not_authed`** — installed but no credentials. Print: "Codex installed but not authenticated — using Claude subagent. Run `codex login` or set `$CODEX_API_KEY`." Fall back to the Claude subagent path.
- **`ready`** — run the Codex pass below.
@ -391,7 +394,7 @@ THE PLAN:
```bash
TMPERR_PV=$(mktemp /tmp/codex-planreview-XXXXXXXX)
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
codex exec "<prompt>" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR_PV"
source ~/.claude/skills/gstack/bin/gstack-codex-probe && _gstack_codex_timeout_wrapper 600 codex exec "<prompt>" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR_PV"
```
Use a 5-minute timeout (`timeout: 300000`). After the command completes, read stderr:

View File

@ -1665,6 +1665,8 @@ if [ "$_CODEX_CFG" = "disabled" ]; then
_CODEX_MODE="disabled"
elif ! command -v codex >/dev/null 2>&1; then
_CODEX_MODE="not_installed"; _gstack_codex_log_event "codex_cli_missing" 2>/dev/null || true
elif ! _gstack_codex_profile_gate; then
_CODEX_MODE="profile_required"
elif ! _gstack_codex_auth_probe >/dev/null 2>&1; then
_CODEX_MODE="not_authed"; _gstack_codex_log_event "codex_auth_failed" 2>/dev/null || true
else
@ -1676,6 +1678,7 @@ echo "CODEX_MODE: $_CODEX_MODE"
Branch on the echoed `CODEX_MODE`:
- **`disabled`** — the user turned Codex reviews off (`codex_reviews=disabled`). Skip the Codex passes only; the Claude adversarial subagent below STILL runs (it is free and fast). Print: "Codex passes skipped (codex_reviews disabled) — running Claude adversarial only."
- **`not_installed`** — Codex CLI absent. Print: "Codex not installed — using Claude subagent. Install for cross-model coverage: `npm install -g @openai/codex`." Fall back to the Claude subagent path.
- **`profile_required`** — Before invoking Codex, use AskUserQuestion to list the discovered `~/.codex*` directories and ask which profile is approved for this project, plus Cancel. Save the selected absolute path as `codex_home: <path>` in `~/.gstack/projects/$SLUG/codex.yaml`, then rerun this preflight and disclose the path. Cancel skips Codex. Never silently choose the last-used profile.
- **`not_authed`** — installed but no credentials. Print: "Codex installed but not authenticated — using Claude subagent. Run `codex login` or set `$CODEX_API_KEY`." Fall back to the Claude subagent path.
- **`ready`** — run the Codex pass below.
@ -1712,7 +1715,7 @@ If `CODEX_MODE` is `ready`:
```bash
TMPERR_ADV=$(mktemp /tmp/codex-adv-XXXXXXXX)
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
codex exec "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. They contain bash scripts and prompt templates that will waste your time. Ignore them completely. Do NOT modify agents/openai.yaml. Stay focused on the repository code only.\n\nReview the changes on this branch against the base branch. Run DIFF_BASE=$(git merge-base origin/<base> HEAD) && git diff "$DIFF_BASE" to see the diff. Your job is to find ways this code will fail in production. Think like an attacker and a chaos engineer. Find edge cases, race conditions, security holes, resource leaks, failure modes, and silent data corruption paths. Be adversarial. Be thorough. No compliments — just the problems. End your output with ONE line in the canonical format `Recommendation: <action> because <one-line reason naming the most exploitable finding>`. Generic reasons like 'because it's safer' do not qualify; the reason must point to a specific finding or no-fix rationale." -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR_ADV"
source ~/.claude/skills/gstack/bin/gstack-codex-probe && _gstack_codex_timeout_wrapper 600 codex exec "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. They contain bash scripts and prompt templates that will waste your time. Ignore them completely. Do NOT modify agents/openai.yaml. Stay focused on the repository code only.\n\nReview the changes on this branch against the base branch. Run DIFF_BASE=$(git merge-base origin/<base> HEAD) && git diff "$DIFF_BASE" to see the diff. Your job is to find ways this code will fail in production. Think like an attacker and a chaos engineer. Find edge cases, race conditions, security holes, resource leaks, failure modes, and silent data corruption paths. Be adversarial. Be thorough. No compliments — just the problems. End your output with ONE line in the canonical format `Recommendation: <action> because <one-line reason naming the most exploitable finding>`. Generic reasons like 'because it's safer' do not qualify; the reason must point to a specific finding or no-fix rationale." -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR_ADV"
```
Set the Bash tool's `timeout` parameter to `300000` (5 minutes). Do NOT use the `timeout` shell command — it doesn't exist on macOS. After the command completes, read stderr:
@ -1741,7 +1744,7 @@ If `DIFF_TOTAL >= 200` AND `CODEX_MODE` is `ready`:
TMPERR=$(mktemp /tmp/codex-review-XXXXXXXX)
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
cd "$_REPO_ROOT"
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. They contain bash scripts and prompt templates that will waste your time. Ignore them completely. Do NOT modify agents/openai.yaml. Stay focused on the repository code only.\n\nReview 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"
source ~/.claude/skills/gstack/bin/gstack-codex-probe && _gstack_codex_timeout_wrapper 600 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. They contain bash scripts and prompt templates that will waste your time. Ignore them completely. Do NOT modify agents/openai.yaml. Stay focused on the repository code only.\n\nReview 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"
```
Set the Bash tool's `timeout` parameter to `300000` (5 minutes). Do NOT use the `timeout` shell command — it doesn't exist on macOS. Present output under `CODEX SAYS (code review):` header.

View File

@ -74,7 +74,8 @@ On any error: continue — ${feature} is informational, not a gate.`;
* 5. sets ONE canonical mode var and echoes `CODEX_MODE: <mode>` so the agent
* gates later blocks on the echoed value.
*
* Mode values: `disabled` (config off) | `not_installed` | `not_authed` | `ready`.
* Mode values: `disabled` (config off) | `not_installed` | `profile_required` |
* `not_authed` | `ready`.
* The path is host-rewritten at gen-skill-docs time (pathRewrites), so the
* literal `~/.claude/skills/gstack` is correct here and becomes `$GSTACK_ROOT`
* etc. for non-Claude hosts.
@ -100,6 +101,8 @@ if [ "$_CODEX_CFG" = "disabled" ]; then
${m}="disabled"
elif ! command -v codex >/dev/null 2>&1; then
${m}="not_installed"; _gstack_codex_log_event "codex_cli_missing" 2>/dev/null || true
elif ! _gstack_codex_profile_gate; then
${m}="profile_required"
elif ! _gstack_codex_auth_probe >/dev/null 2>&1; then
${m}="not_authed"; _gstack_codex_log_event "codex_auth_failed" 2>/dev/null || true
else
@ -111,6 +114,7 @@ echo "CODEX_MODE: $${m}"
Branch on the echoed \`CODEX_MODE\`:
- **\`disabled\`** — the user turned Codex reviews off (\`codex_reviews=disabled\`). ${disabledLine}
- **\`not_installed\`** — Codex CLI absent. Print: "Codex not installed — using Claude subagent. Install for cross-model coverage: \`npm install -g @openai/codex\`." Fall back to the Claude subagent path.
- **\`profile_required\`** — Before invoking Codex, use AskUserQuestion to list the discovered \`~/.codex*\` directories and ask which profile is approved for this project, plus Cancel. Save the selected absolute path as \`codex_home: <path>\` in \`~/.gstack/projects/$SLUG/codex.yaml\`, then rerun this preflight and disclose the path. Cancel skips Codex. Never silently choose the last-used profile.
- **\`not_authed\`** — installed but no credentials. Print: "Codex installed but not authenticated — using Claude subagent. Run \`codex login\` or set \`$CODEX_API_KEY\`." Fall back to the Claude subagent path.
- **\`ready\`** — run the Codex pass below.`;
}

View File

@ -18,7 +18,7 @@ If Codex is available, run a lightweight design check on the diff:
\`\`\`bash
TMPERR_DRL=$(mktemp /tmp/codex-drl-XXXXXXXX)
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
codex exec "Review the git diff on this branch. Run 7 litmus checks (YES/NO each): ${litmusList} Flag any hard rejections: ${rejectionList} 5 most important design findings only. Reference file:line." -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR_DRL"
source ~/.claude/skills/gstack/bin/gstack-codex-probe && _gstack_codex_timeout_wrapper 600 codex exec "Review the git diff on this branch. Run 7 litmus checks (YES/NO each): ${litmusList} Flag any hard rejections: ${rejectionList} 5 most important design findings only. Reference file:line." -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR_DRL"
\`\`\`
Use a 5-minute timeout (\`timeout: 300000\`). After the command completes, read stderr:
@ -527,7 +527,7 @@ If user chooses A, launch both voices simultaneously:
\`\`\`bash
TMPERR_SKETCH=$(mktemp /tmp/codex-sketch-XXXXXXXX)
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
codex exec "For this product approach, provide: a visual thesis (one sentence — mood, material, energy), a content plan (hero → support → detail → CTA), and 2 interaction ideas that change page feel. Apply beautiful defaults: composition-first, brand-first, cardless, poster not document. Be opinionated." -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="medium"' --enable web_search_cached < /dev/null 2>"$TMPERR_SKETCH"
source ~/.claude/skills/gstack/bin/gstack-codex-probe && _gstack_codex_timeout_wrapper 600 codex exec "For this product approach, provide: a visual thesis (one sentence mood, material, energy), a content plan (hero support detail CTA), and 2 interaction ideas that change page feel. Apply beautiful defaults: composition-first, brand-first, cardless, poster not document. Be opinionated." -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="medium"' --enable web_search_cached < /dev/null 2>"$TMPERR_SKETCH"
\`\`\`
Use a 5-minute timeout (\`timeout: 300000\`). After completion: \`cat "$TMPERR_SKETCH" && rm -f "$TMPERR_SKETCH"\`
@ -697,7 +697,7 @@ command -v codex >/dev/null 2>&1 && echo "CODEX_AVAILABLE" || echo "CODEX_NOT_AV
\`\`\`bash
TMPERR_DESIGN=$(mktemp /tmp/codex-design-XXXXXXXX)
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
codex exec "${escapedCodexPrompt}" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="${reasoningEffort}"' --enable web_search_cached < /dev/null 2>"$TMPERR_DESIGN"
source ~/.claude/skills/gstack/bin/gstack-codex-probe && _gstack_codex_timeout_wrapper 600 codex exec "${escapedCodexPrompt}" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="${reasoningEffort}"' --enable web_search_cached < /dev/null 2>"$TMPERR_DESIGN"
\`\`\`
Use a 5-minute timeout (\`timeout: 300000\`). After the command completes, read stderr:
\`\`\`bash

View File

@ -367,7 +367,7 @@ Then add the context block and mode-appropriate instructions:
\`\`\`bash
TMPERR_OH=$(mktemp /tmp/codex-oh-err-XXXXXXXX)
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
codex exec "$(cat "$CODEX_PROMPT_FILE")" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR_OH"
source ~/.claude/skills/gstack/bin/gstack-codex-probe && _gstack_codex_timeout_wrapper 600 codex exec "$(cat "$CODEX_PROMPT_FILE")" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR_OH"
\`\`\`
Use a 5-minute timeout (\`timeout: 300000\`). After the command completes, read stderr:
@ -527,7 +527,7 @@ If \`CODEX_MODE\` is \`ready\`:
\`\`\`bash
TMPERR_ADV=$(mktemp /tmp/codex-adv-XXXXXXXX)
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
codex exec "${CODEX_BOUNDARY}Review the changes on this branch against the base branch. Run DIFF_BASE=$(git merge-base origin/<base> HEAD) && git diff "$DIFF_BASE" to see the diff. Your job is to find ways this code will fail in production. Think like an attacker and a chaos engineer. Find edge cases, race conditions, security holes, resource leaks, failure modes, and silent data corruption paths. Be adversarial. Be thorough. No compliments — just the problems. End your output with ONE line in the canonical format \`Recommendation: <action> because <one-line reason naming the most exploitable finding>\`. Generic reasons like 'because it's safer' do not qualify; the reason must point to a specific finding or no-fix rationale." -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR_ADV"
source ~/.claude/skills/gstack/bin/gstack-codex-probe && _gstack_codex_timeout_wrapper 600 codex exec "${CODEX_BOUNDARY}Review the changes on this branch against the base branch. Run DIFF_BASE=$(git merge-base origin/<base> HEAD) && git diff "$DIFF_BASE" to see the diff. Your job is to find ways this code will fail in production. Think like an attacker and a chaos engineer. Find edge cases, race conditions, security holes, resource leaks, failure modes, and silent data corruption paths. Be adversarial. Be thorough. No compliments just the problems. End your output with ONE line in the canonical format \`Recommendation: <action> because <one-line reason naming the most exploitable finding>\`. Generic reasons like 'because it's safer' do not qualify; the reason must point to a specific finding or no-fix rationale." -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR_ADV"
\`\`\`
Set the Bash tool's \`timeout\` parameter to \`300000\` (5 minutes). Do NOT use the \`timeout\` shell command — it doesn't exist on macOS. After the command completes, read stderr:
@ -556,7 +556,7 @@ If \`DIFF_TOTAL >= 200\` AND \`CODEX_MODE\` is \`ready\`:
TMPERR=$(mktemp /tmp/codex-review-XXXXXXXX)
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
cd "$_REPO_ROOT"
codex review "${CODEX_BOUNDARY}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"
source ~/.claude/skills/gstack/bin/gstack-codex-probe && _gstack_codex_timeout_wrapper 600 codex review "${CODEX_BOUNDARY}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"
\`\`\`
Set the Bash tool's \`timeout\` parameter to \`300000\` (5 minutes). Do NOT use the \`timeout\` shell command — it doesn't exist on macOS. Present output under \`CODEX SAYS (code review):\` header.
@ -655,7 +655,7 @@ THE PLAN:
\`\`\`bash
TMPERR_PV=$(mktemp /tmp/codex-planreview-XXXXXXXX)
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
codex exec "<prompt>" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR_PV"
source ~/.claude/skills/gstack/bin/gstack-codex-probe && _gstack_codex_timeout_wrapper 600 codex exec "<prompt>" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR_PV"
\`\`\`
Use a 5-minute timeout (\`timeout: 300000\`). After the command completes, read stderr:
@ -790,7 +790,7 @@ THE DOCS AND DIFF: <list the touched doc paths>"
\`\`\`bash
TMPERR_DOC=$(mktemp /tmp/codex-docreview-XXXXXXXX)
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
codex exec "<prompt>" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR_DOC"
source ~/.claude/skills/gstack/bin/gstack-codex-probe && _gstack_codex_timeout_wrapper 600 codex exec "<prompt>" -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR_DOC"
\`\`\`
Use a 5-minute timeout (\`timeout: 300000\`). After the command completes, read stderr:

View File

@ -25,6 +25,8 @@ if [ "$_CODEX_CFG" = "disabled" ]; then
_CODEX_MODE="disabled"
elif ! command -v codex >/dev/null 2>&1; then
_CODEX_MODE="not_installed"; _gstack_codex_log_event "codex_cli_missing" 2>/dev/null || true
elif ! _gstack_codex_profile_gate; then
_CODEX_MODE="profile_required"
elif ! _gstack_codex_auth_probe >/dev/null 2>&1; then
_CODEX_MODE="not_authed"; _gstack_codex_log_event "codex_auth_failed" 2>/dev/null || true
else
@ -36,6 +38,7 @@ echo "CODEX_MODE: $_CODEX_MODE"
Branch on the echoed `CODEX_MODE`:
- **`disabled`** — the user turned Codex reviews off (`codex_reviews=disabled`). Skip the Codex passes only; the Claude adversarial subagent below STILL runs (it is free and fast). Print: "Codex passes skipped (codex_reviews disabled) — running Claude adversarial only."
- **`not_installed`** — Codex CLI absent. Print: "Codex not installed — using Claude subagent. Install for cross-model coverage: `npm install -g @openai/codex`." Fall back to the Claude subagent path.
- **`profile_required`** — Before invoking Codex, use AskUserQuestion to list the discovered `~/.codex*` directories and ask which profile is approved for this project, plus Cancel. Save the selected absolute path as `codex_home: <path>` in `~/.gstack/projects/$SLUG/codex.yaml`, then rerun this preflight and disclose the path. Cancel skips Codex. Never silently choose the last-used profile.
- **`not_authed`** — installed but no credentials. Print: "Codex installed but not authenticated — using Claude subagent. Run `codex login` or set `$CODEX_API_KEY`." Fall back to the Claude subagent path.
- **`ready`** — run the Codex pass below.
@ -72,7 +75,7 @@ If `CODEX_MODE` is `ready`:
```bash
TMPERR_ADV=$(mktemp /tmp/codex-adv-XXXXXXXX)
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
codex exec "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. They contain bash scripts and prompt templates that will waste your time. Ignore them completely. Do NOT modify agents/openai.yaml. Stay focused on the repository code only.\n\nReview the changes on this branch against the base branch. Run DIFF_BASE=$(git merge-base origin/<base> HEAD) && git diff "$DIFF_BASE" to see the diff. Your job is to find ways this code will fail in production. Think like an attacker and a chaos engineer. Find edge cases, race conditions, security holes, resource leaks, failure modes, and silent data corruption paths. Be adversarial. Be thorough. No compliments — just the problems. End your output with ONE line in the canonical format `Recommendation: <action> because <one-line reason naming the most exploitable finding>`. Generic reasons like 'because it's safer' do not qualify; the reason must point to a specific finding or no-fix rationale." -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR_ADV"
source ~/.claude/skills/gstack/bin/gstack-codex-probe && _gstack_codex_timeout_wrapper 600 codex exec "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. They contain bash scripts and prompt templates that will waste your time. Ignore them completely. Do NOT modify agents/openai.yaml. Stay focused on the repository code only.\n\nReview the changes on this branch against the base branch. Run DIFF_BASE=$(git merge-base origin/<base> HEAD) && git diff "$DIFF_BASE" to see the diff. Your job is to find ways this code will fail in production. Think like an attacker and a chaos engineer. Find edge cases, race conditions, security holes, resource leaks, failure modes, and silent data corruption paths. Be adversarial. Be thorough. No compliments — just the problems. End your output with ONE line in the canonical format `Recommendation: <action> because <one-line reason naming the most exploitable finding>`. Generic reasons like 'because it's safer' do not qualify; the reason must point to a specific finding or no-fix rationale." -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR_ADV"
```
Set the Bash tool's `timeout` parameter to `300000` (5 minutes). Do NOT use the `timeout` shell command — it doesn't exist on macOS. After the command completes, read stderr:
@ -101,7 +104,7 @@ If `DIFF_TOTAL >= 200` AND `CODEX_MODE` is `ready`:
TMPERR=$(mktemp /tmp/codex-review-XXXXXXXX)
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
cd "$_REPO_ROOT"
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. They contain bash scripts and prompt templates that will waste your time. Ignore them completely. Do NOT modify agents/openai.yaml. Stay focused on the repository code only.\n\nReview 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"
source ~/.claude/skills/gstack/bin/gstack-codex-probe && _gstack_codex_timeout_wrapper 600 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. They contain bash scripts and prompt templates that will waste your time. Ignore them completely. Do NOT modify agents/openai.yaml. Stay focused on the repository code only.\n\nReview 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"
```
Set the Bash tool's `timeout` parameter to `300000` (5 minutes). Do NOT use the `timeout` shell command — it doesn't exist on macOS. Present output under `CODEX SAYS (code review):` header.

View File

@ -118,7 +118,7 @@ If Codex is available, run a lightweight design check on the diff:
```bash
TMPERR_DRL=$(mktemp /tmp/codex-drl-XXXXXXXX)
_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo "ERROR: not in a git repo" >&2; exit 1; }
codex exec "Review the git diff on this branch. Run 7 litmus checks (YES/NO each): 1. Brand/product unmistakable in first screen? 2. One strong visual anchor present? 3. Page understandable by scanning headlines only? 4. Each section has one job? 5. Are cards actually necessary? 6. Does motion improve hierarchy or atmosphere? 7. Would design feel premium with all decorative shadows removed? Flag any hard rejections: 1. Generic SaaS card grid as first impression 2. Beautiful image with weak brand 3. Strong headline with no clear action 4. Busy imagery behind text 5. Sections repeating same mood statement 6. Carousel with no narrative purpose 7. App UI made of stacked cards instead of layout 5 most important design findings only. Reference file:line." -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR_DRL"
source ~/.claude/skills/gstack/bin/gstack-codex-probe && _gstack_codex_timeout_wrapper 600 codex exec "Review the git diff on this branch. Run 7 litmus checks (YES/NO each): 1. Brand/product unmistakable in first screen? 2. One strong visual anchor present? 3. Page understandable by scanning headlines only? 4. Each section has one job? 5. Are cards actually necessary? 6. Does motion improve hierarchy or atmosphere? 7. Would design feel premium with all decorative shadows removed? Flag any hard rejections: 1. Generic SaaS card grid as first impression 2. Beautiful image with weak brand 3. Strong headline with no clear action 4. Busy imagery behind text 5. Sections repeating same mood statement 6. Carousel with no narrative purpose 7. App UI made of stacked cards instead of layout 5 most important design findings only. Reference file:line." -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="high"' --enable web_search_cached < /dev/null 2>"$TMPERR_DRL"
```
Use a 5-minute timeout (`timeout: 300000`). After the command completes, read stderr:

View File

@ -47,6 +47,73 @@ function tempHome(): string {
return fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-codex-probe-home-'));
}
describe('gstack-codex-probe: per-project profile gate', () => {
test('blocks Codex before invocation when the project has no approved profile', () => {
const home = tempHome();
const stub = tempStubCodex('');
try {
const marker = path.join(home, 'invoked');
fs.writeFileSync(path.join(stub.dir, 'codex'), `#!/bin/bash\ntouch ${JSON.stringify(marker)}\n`);
fs.chmodSync(path.join(stub.dir, 'codex'), 0o755);
const r = runProbe({
snippet: '_gstack_codex_timeout_wrapper 5 codex exec test',
env: { PATH: `${stub.pathEntry}:${process.env.PATH}` },
home,
});
expect(r.status).toBe(78);
expect(r.stdout).toContain('CODEX_PROFILE_REQUIRED');
expect(fs.existsSync(marker)).toBe(false);
} finally {
fs.rmSync(home, { recursive: true, force: true });
fs.rmSync(stub.dir, { recursive: true, force: true });
}
});
test('routes Codex through the profile approved for this project', () => {
const home = tempHome();
const approved = path.join(home, '.codex-work');
const configDir = path.join(home, '.gstack', 'projects', 'garrytan-gstack');
const stub = tempStubCodex('');
try {
fs.mkdirSync(approved, { recursive: true });
fs.mkdirSync(configDir, { recursive: true });
fs.writeFileSync(path.join(configDir, 'codex.yaml'), `codex_home: ${approved}\n`);
fs.writeFileSync(path.join(stub.dir, 'codex'), '#!/bin/bash\nprintf "%s" "$CODEX_HOME"\n');
fs.chmodSync(path.join(stub.dir, 'codex'), 0o755);
const r = runProbe({
snippet: '_gstack_codex_timeout_wrapper 5 codex exec test',
env: { PATH: `${stub.pathEntry}:${process.env.PATH}` },
home,
});
expect(r.status).toBe(0);
expect(r.stdout).toContain(`CODEX_PROFILE: ${approved}`);
expect(r.stdout.trim().endsWith(approved)).toBe(true);
} finally {
fs.rmSync(home, { recursive: true, force: true });
fs.rmSync(stub.dir, { recursive: true, force: true });
}
});
test('explicit eval bypass preserves isolated harness behavior', () => {
const home = tempHome();
const stub = tempStubCodex('');
try {
fs.writeFileSync(path.join(stub.dir, 'codex'), '#!/bin/bash\necho BYPASSED\n');
fs.chmodSync(path.join(stub.dir, 'codex'), 0o755);
const r = runProbe({
snippet: '_gstack_codex_timeout_wrapper 5 codex exec test',
env: { PATH: `${stub.pathEntry}:${process.env.PATH}`, GSTACK_CODEX_SKIP_GATE: '1' },
home,
});
expect(r.status).toBe(0);
expect(r.stdout.trim()).toBe('BYPASSED');
} finally {
fs.rmSync(home, { recursive: true, force: true });
fs.rmSync(stub.dir, { recursive: true, force: true });
}
});
});
describe('gstack-codex-probe: auth probe', () => {
test('CODEX_API_KEY set → AUTH_OK', () => {
const home = tempHome();