diff --git a/codex/SKILL.md b/codex/SKILL.md
index 826e50511..d74a52588 100644
--- a/codex/SKILL.md
+++ b/codex/SKILL.md
@@ -814,7 +814,7 @@ assumptions, catches things you might miss. Present its output faithfully, not s
## Step 0.4: Check codex binary
```bash
-CODEX_BIN=$(which codex 2>/dev/null || echo "")
+CODEX_BIN=$(command -v codex || echo "")
[ -z "$CODEX_BIN" ] && echo "NOT_FOUND" || echo "FOUND: $CODEX_BIN"
```
diff --git a/codex/SKILL.md.tmpl b/codex/SKILL.md.tmpl
index 108fca055..6cda8a5a2 100644
--- a/codex/SKILL.md.tmpl
+++ b/codex/SKILL.md.tmpl
@@ -42,7 +42,7 @@ assumptions, catches things you might miss. Present its output faithfully, not s
## Step 0.4: Check codex binary
```bash
-CODEX_BIN=$(which codex 2>/dev/null || echo "")
+CODEX_BIN=$(command -v codex || echo "")
[ -z "$CODEX_BIN" ] && echo "NOT_FOUND" || echo "FOUND: $CODEX_BIN"
```
diff --git a/design-consultation/SKILL.md b/design-consultation/SKILL.md
index 00a5f0f2e..bc52edc10 100644
--- a/design-consultation/SKILL.md
+++ b/design-consultation/SKILL.md
@@ -1090,7 +1090,7 @@ If user chooses B, skip this step and continue.
**Check Codex availability:**
```bash
-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"
```
**If Codex is available**, launch both voices simultaneously:
diff --git a/design-review/SKILL.md b/design-review/SKILL.md
index 91603dd2e..b584ada8f 100644
--- a/design-review/SKILL.md
+++ b/design-review/SKILL.md
@@ -1687,7 +1687,7 @@ Record baseline design score and AI slop score at end of Phase 6.
**Check Codex availability:**
```bash
-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"
```
**If Codex is available**, launch both voices simultaneously:
diff --git a/office-hours/SKILL.md b/office-hours/SKILL.md
index c4acb9ea8..b8b6fe1f9 100644
--- a/office-hours/SKILL.md
+++ b/office-hours/SKILL.md
@@ -1219,7 +1219,7 @@ Use AskUserQuestion to confirm. If the user disagrees with a premise, revise und
**Binary check first:**
```bash
-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"
```
Use AskUserQuestion (regardless of codex availability):
@@ -1491,7 +1491,7 @@ The screenshot file at `/tmp/gstack-sketch.png` can be referenced by downstream
After the wireframe is approved, offer outside design perspectives:
```bash
-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"
```
If Codex is available, use AskUserQuestion:
diff --git a/plan-ceo-review/SKILL.md b/plan-ceo-review/SKILL.md
index 91c1cfc79..a0b24ef99 100644
--- a/plan-ceo-review/SKILL.md
+++ b/plan-ceo-review/SKILL.md
@@ -1613,7 +1613,7 @@ thorough review.
**Check tool availability:**
```bash
-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"
```
Use AskUserQuestion:
diff --git a/plan-design-review/SKILL.md b/plan-design-review/SKILL.md
index 580268767..45b56bf4d 100644
--- a/plan-design-review/SKILL.md
+++ b/plan-design-review/SKILL.md
@@ -1241,7 +1241,7 @@ If user chooses B, skip this step and continue.
**Check Codex availability:**
```bash
-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"
```
**If Codex is available**, launch both voices simultaneously:
diff --git a/plan-devex-review/SKILL.md b/plan-devex-review/SKILL.md
index 29014b4a4..371d07a75 100644
--- a/plan-devex-review/SKILL.md
+++ b/plan-devex-review/SKILL.md
@@ -1585,7 +1585,7 @@ thorough review.
**Check tool availability:**
```bash
-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"
```
Use AskUserQuestion:
diff --git a/plan-eng-review/SKILL.md b/plan-eng-review/SKILL.md
index 1dbc3c96e..925daab13 100644
--- a/plan-eng-review/SKILL.md
+++ b/plan-eng-review/SKILL.md
@@ -1214,7 +1214,7 @@ thorough review.
**Check tool availability:**
```bash
-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"
```
Use AskUserQuestion:
diff --git a/review/SKILL.md b/review/SKILL.md
index ee065f032..d7e84cbaa 100644
--- a/review/SKILL.md
+++ b/review/SKILL.md
@@ -1578,7 +1578,7 @@ DIFF_BASE=$(git merge-base origin/ HEAD)
DIFF_INS=$(git diff "$DIFF_BASE" --stat | tail -1 | grep -oE '[0-9]+ insertion' | grep -oE '[0-9]+' || echo "0")
DIFF_DEL=$(git diff "$DIFF_BASE" --stat | tail -1 | grep -oE '[0-9]+ deletion' | grep -oE '[0-9]+' || echo "0")
DIFF_TOTAL=$((DIFF_INS + DIFF_DEL))
-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"
# Legacy opt-out — only gates Codex passes, Claude always runs
OLD_CFG=$(~/.claude/skills/gstack/bin/gstack-config get codex_reviews 2>/dev/null || true)
echo "DIFF_SIZE: $DIFF_TOTAL"
diff --git a/scripts/resolvers/design.ts b/scripts/resolvers/design.ts
index fc6d6ecee..33247aab5 100644
--- a/scripts/resolvers/design.ts
+++ b/scripts/resolvers/design.ts
@@ -10,7 +10,7 @@ export function generateDesignReviewLite(ctx: TemplateContext): string {
7. **Codex design voice** (optional, automatic if available):
\`\`\`bash
-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"
\`\`\`
If Codex is available, run a lightweight design check on the diff:
@@ -512,7 +512,7 @@ The screenshot file at \`/tmp/gstack-sketch.png\` can be referenced by downstrea
After the wireframe is approved, offer outside design perspectives:
\`\`\`bash
-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"
\`\`\`
If Codex is available, use AskUserQuestion:
@@ -688,7 +688,7 @@ ${optInSection}
**Check Codex availability:**
\`\`\`bash
-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"
\`\`\`
**If Codex is available**, launch both voices simultaneously:
diff --git a/scripts/resolvers/review.ts b/scripts/resolvers/review.ts
index 9839a2023..0c7cb8230 100644
--- a/scripts/resolvers/review.ts
+++ b/scripts/resolvers/review.ts
@@ -311,7 +311,7 @@ export function generateCodexSecondOpinion(ctx: TemplateContext): string {
**Binary check first:**
\`\`\`bash
-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"
\`\`\`
Use AskUserQuestion (regardless of codex availability):
@@ -471,7 +471,7 @@ DIFF_BASE=$(git merge-base origin/ HEAD)
DIFF_INS=$(git diff "$DIFF_BASE" --stat | tail -1 | grep -oE '[0-9]+ insertion' | grep -oE '[0-9]+' || echo "0")
DIFF_DEL=$(git diff "$DIFF_BASE" --stat | tail -1 | grep -oE '[0-9]+ deletion' | grep -oE '[0-9]+' || echo "0")
DIFF_TOTAL=$((DIFF_INS + DIFF_DEL))
-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"
# Legacy opt-out — only gates Codex passes, Claude always runs
OLD_CFG=$(~/.claude/skills/gstack/bin/gstack-config get codex_reviews 2>/dev/null || true)
echo "DIFF_SIZE: $DIFF_TOTAL"
@@ -600,7 +600,7 @@ thorough review.
**Check tool availability:**
\`\`\`bash
-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"
\`\`\`
Use AskUserQuestion:
diff --git a/ship/SKILL.md b/ship/SKILL.md
index 0cb0fe529..481f1bfd4 100644
--- a/ship/SKILL.md
+++ b/ship/SKILL.md
@@ -1962,7 +1962,7 @@ Substitute: TIMESTAMP = ISO 8601 datetime, STATUS = "clean" if 0 findings or "is
7. **Codex design voice** (optional, automatic if available):
```bash
-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"
```
If Codex is available, run a lightweight design check on the diff:
@@ -2317,7 +2317,7 @@ DIFF_BASE=$(git merge-base origin/ HEAD)
DIFF_INS=$(git diff "$DIFF_BASE" --stat | tail -1 | grep -oE '[0-9]+ insertion' | grep -oE '[0-9]+' || echo "0")
DIFF_DEL=$(git diff "$DIFF_BASE" --stat | tail -1 | grep -oE '[0-9]+ deletion' | grep -oE '[0-9]+' || echo "0")
DIFF_TOTAL=$((DIFF_INS + DIFF_DEL))
-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"
# Legacy opt-out — only gates Codex passes, Claude always runs
OLD_CFG=$(~/.claude/skills/gstack/bin/gstack-config get codex_reviews 2>/dev/null || true)
echo "DIFF_SIZE: $DIFF_TOTAL"
diff --git a/test/skill-e2e-plan.test.ts b/test/skill-e2e-plan.test.ts
index cb630ca97..d6f58416e 100644
--- a/test/skill-e2e-plan.test.ts
+++ b/test/skill-e2e-plan.test.ts
@@ -775,8 +775,8 @@ Write your summary to ${testDir}/${testName}-summary.md`,
expect(fs.existsSync(summaryPath)).toBe(true);
const summary = fs.readFileSync(summaryPath, 'utf-8').toLowerCase();
- // All skills should have codex availability check
- expect(summary).toMatch(/which codex/);
+ // All skills should have codex availability check (command -v per #1197)
+ expect(summary).toMatch(/command -v codex/);
// All skills should have fallback behavior
expect(summary).toMatch(/fallback|subagent|unavailable|not available|skip/);
// All skills should show it's optional/non-blocking
diff --git a/test/skill-validation.test.ts b/test/skill-validation.test.ts
index ed3c32611..7df535552 100644
--- a/test/skill-validation.test.ts
+++ b/test/skill-validation.test.ts
@@ -1325,10 +1325,14 @@ describe('Codex skill', () => {
expect(content).toContain('gstack-review-log');
});
- test('codex/SKILL.md uses which for binary discovery, not hardcoded path', () => {
+ test('codex/SKILL.md uses command -v for binary discovery, not hardcoded path', () => {
const content = fs.readFileSync(path.join(ROOT, 'codex', 'SKILL.md'), 'utf-8');
- expect(content).toContain('which codex');
+ expect(content).toContain('command -v codex');
expect(content).not.toContain('/opt/homebrew/bin/codex');
+ // Defensive: catch any future regression that reintroduces `which codex`,
+ // which fails in environments where `which` isn't on PATH (some Windows
+ // shells, BusyBox-only containers). #1197.
+ expect(content).not.toContain('which codex');
});
test('codex/SKILL.md contains error handling for missing binary and auth', () => {