Summary
Several skills gate Codex-backed passes behind which codex 2>/dev/null && echo "CODEX_AVAILABLE" || echo "CODEX_NOT_AVAILABLE". In the wild this returns CODEX_NOT_AVAILABLE even when Codex is installed and on PATH, silently skipping the Codex adversarial pass without telling the user.
Hit this on gstack v1.3.0.0 → v1.12.2.0 upgrade path during a /review run. Codex CLI @openai/codex@0.125.0 was installed globally under nvm (/home/btallman/.nvm/versions/node/v24.12.0/bin/codex, on PATH). My first invocation in the Claude Code bash tool returned /bin/bash: line 1: codex: command not found; subsequent invocations after hash -r resolved fine. Net effect: the /review output falsely said "Codex CLI not installed — running Claude adversarial only" and I approved a PR without the Codex pass.
Reproducer
# Confirmed on Ubuntu/Pop!_OS, bash 5.x, nvm-managed node
$ which codex 2>&1
/bin/bash: line 1: codex: command not found
$ command -v codex
/home/btallman/.nvm/versions/node/v24.12.0/bin/codex
$ type codex
codex is /home/btallman/.nvm/versions/node/v24.12.0/bin/codex
which is an external utility on most distros and should not be affected by bash's hash table — but in non-interactive shells spawned by tool runners, PATH hydration and hash state can diverge in ways that cause which to miss binaries that command -v and type find correctly. Regardless of root cause, gstack is making its Codex gate depend on a non-POSIX utility with platform-dependent behavior.
Root cause
which is not specified by POSIX. Its behavior varies:
- Debian/Ubuntu ship
/usr/bin/which (debianutils) which scans PATH
- Some systems alias
which to a bash function that reads the hash table
- On macOS it's a simple csh-compatible script
- Busybox shells don't ship it at all
command -v IS POSIX-specified and is the correct way to check for an executable in POSIX/bash. It uses the same resolution the shell would use to actually run the command — so if command -v X returns a path, X is runnable; if not, it isn't.
Affected files (15 callsites across 9 skills + resolver scripts + test fixtures)
review/SKILL.md:1821
ship/SKILL.md:2194, 2547
office-hours/SKILL.md:1473, 1743
plan-ceo-review/SKILL.md:1868
plan-eng-review/SKILL.md:1481
design-consultation/SKILL.md:1348
codex/SKILL.md:1095, codex/SKILL.md.tmpl:45
scripts/resolvers/review.ts:269, 428, 557
scripts/resolvers/design.ts:13, 515, 691
test/fixtures/golden-ship-claude.md:1649, 1991
test/fixtures/golden/factory-ship-SKILL.md:2185
test/skill-validation.test.ts:1320 # asserts content contains 'which codex'
test/skill-e2e-plan.test.ts:779 # same
Suggested fix
Replace the pattern everywhere:
- which codex 2>/dev/null && echo "CODEX_AVAILABLE" || echo "CODEX_NOT_AVAILABLE"
+ command -v codex >/dev/null 2>&1 && echo "CODEX_AVAILABLE" || echo "CODEX_NOT_AVAILABLE"
And the codex/SKILL.md.tmpl:45 variant:
- CODEX_BIN=$(which codex 2>/dev/null || echo "")
+ CODEX_BIN=$(command -v codex 2>/dev/null || echo "")
Update the two test fixtures to match the new string, and update the two test assertions in skill-validation.test.ts:1320 / skill-e2e-plan.test.ts:779 to assert 'command -v codex' instead of 'which codex'.
Should also sweep for any other which <bin> patterns in skill preambles — which gh, which codex, which npx, etc. all have the same reliability problem.
Impact
Silent — user sees no error, just gets a weaker review (Codex pass skipped) and doesn't know why. For /review and /ship that means a whole second opinion is missing from the PR gate. Blast radius is every environment where which behaves differently from command -v, which includes nvm-managed node installs in non-interactive tool-runner shells (Claude Code, Codex CLI, Cursor's terminal, etc.).
Workaround
Run hash -r before invoking a skill that uses Codex, or manually source your shell rc file. Not a fix — it's a band-aid for the specific hash-cache manifestation.
Version
- gstack: 1.12.2.0 (current main, commit 6209163)
- OS: Linux (Pop!_OS, Ubuntu-based)
- Shell: bash 5.x, non-interactive (Claude Code bash tool)
- Node: v24.12.0 via nvm
- Codex: @openai/codex@0.125.0
Summary
Several skills gate Codex-backed passes behind
which codex 2>/dev/null && echo "CODEX_AVAILABLE" || echo "CODEX_NOT_AVAILABLE". In the wild this returnsCODEX_NOT_AVAILABLEeven when Codex is installed and on PATH, silently skipping the Codex adversarial pass without telling the user.Hit this on gstack v1.3.0.0 → v1.12.2.0 upgrade path during a
/reviewrun. Codex CLI@openai/codex@0.125.0was installed globally under nvm (/home/btallman/.nvm/versions/node/v24.12.0/bin/codex, on PATH). My first invocation in the Claude Code bash tool returned/bin/bash: line 1: codex: command not found; subsequent invocations afterhash -rresolved fine. Net effect: the/reviewoutput falsely said "Codex CLI not installed — running Claude adversarial only" and I approved a PR without the Codex pass.Reproducer
whichis an external utility on most distros and should not be affected by bash's hash table — but in non-interactive shells spawned by tool runners, PATH hydration and hash state can diverge in ways that causewhichto miss binaries thatcommand -vandtypefind correctly. Regardless of root cause, gstack is making its Codex gate depend on a non-POSIX utility with platform-dependent behavior.Root cause
whichis not specified by POSIX. Its behavior varies:/usr/bin/which(debianutils) which scans PATHwhichto a bash function that reads the hash tablecommand -vIS POSIX-specified and is the correct way to check for an executable in POSIX/bash. It uses the same resolution the shell would use to actually run the command — so ifcommand -v Xreturns a path,Xis runnable; if not, it isn't.Affected files (15 callsites across 9 skills + resolver scripts + test fixtures)
Suggested fix
Replace the pattern everywhere:
And the
codex/SKILL.md.tmpl:45variant:Update the two test fixtures to match the new string, and update the two test assertions in
skill-validation.test.ts:1320/skill-e2e-plan.test.ts:779to assert'command -v codex'instead of'which codex'.Should also sweep for any other
which <bin>patterns in skill preambles —which gh,which codex,which npx, etc. all have the same reliability problem.Impact
Silent — user sees no error, just gets a weaker review (Codex pass skipped) and doesn't know why. For
/reviewand/shipthat means a whole second opinion is missing from the PR gate. Blast radius is every environment wherewhichbehaves differently fromcommand -v, which includes nvm-managed node installs in non-interactive tool-runner shells (Claude Code, Codex CLI, Cursor's terminal, etc.).Workaround
Run
hash -rbefore invoking a skill that uses Codex, or manually source your shell rc file. Not a fix — it's a band-aid for the specific hash-cache manifestation.Version