Skip to content

which codex detection in review/ship/office-hours/plan-ceo-review/design skills unreliable — use command -v instead #1193

@btallman

Description

@btallman

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions