mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-05 16:34:00 +01:00
fix: sourced helpers share one self-locate block and fail loudly when they cannot locate themselves (#3024 rework)
The codex probe and the egress lib now carry one byte-identical block: BASH_SOURCE under bash or %x under zsh, made absolute with $PWD, else a set GSTACK_ROOT that holds the helper. Anything else (dash, sh, a shell that cannot name the file) prints "gstack: cannot locate <helper> (shell: ...)" with the docs/troubleshooting.md#sourced-helper-location URL and returns before any bash- or zsh-only syntax. The Claude-only default path and the $(pwd) fallback are gone. The generated Codex preflight dot-sources the probe and, when it cannot load, reports CODEX_MODE: helper_unavailable with the helper's own line instead of falling through to not_authed. The class guard enumerates nested sources, makes real resolver, auth, state-root and egress-home calls under bash and zsh (absolute, relative then cd, bare name via PATH), covers dash and an unknown shell, runs the full preflight, and lints BASH_SOURCE to the shared block plus other bash-only constructs. Co-authored-by: marcosdetry <marcosdetry@users.noreply.github.com> Co-authored-by: akshaykalia <akshaykalia@users.noreply.github.com> Co-authored-by: scottgigante-hubflow <scottgigante-hubflow@users.noreply.github.com> Co-authored-by: woodsonl <woodsonl@users.noreply.github.com> Co-authored-by: bezdekt <bezdekt@users.noreply.github.com>
This commit is contained in:
1 parent
a08cb03782
commit
5884117c02
16 files changed
+344
-74
No files matched your search
+5
-1
@@ -757,13 +757,16 @@ Review skills will load at each phase entry. Starting full review pipeline with
|
||||
# Codex preflight: one block (functions sourced here don't persist to later blocks).
|
||||
_TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || echo off)
|
||||
_CODEX_CFG=$(~/.claude/skills/gstack/bin/gstack-config get codex_reviews 2>/dev/null || echo enabled)
|
||||
source ~/.claude/skills/gstack/bin/gstack-codex-probe 2>/dev/null || true
|
||||
_gstack_helper_error=""
|
||||
. ~/.claude/skills/gstack/bin/gstack-codex-probe 2>/dev/null || _gstack_helper_error="${_gstack_helper_error:-gstack: cannot load gstack-codex-probe; re-run ./setup. https://github.com/garrytan/gstack/blob/main/docs/troubleshooting.md#sourced-helper-location}"
|
||||
if [ "$_CODEX_CFG" = "disabled" ]; then
|
||||
_CODEX_MODE="disabled"
|
||||
elif { [ -n "${CODEX_THREAD_ID:-}" ] || [ -n "${CODEX_SANDBOX:-}" ] || [ "${GSTACK_ACTIVE_HOST:-}" = codex ]; }; then
|
||||
_CODEX_MODE="under_codex"
|
||||
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 [ -n "$_gstack_helper_error" ]; then
|
||||
_CODEX_MODE="helper_unavailable"; echo "$_gstack_helper_error"
|
||||
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
|
||||
@@ -790,6 +793,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."
|
||||
- **`helper_unavailable`** — the helper could not load; relay the line above (cause and fix). Keep the required Claude adversarial pass; do not dispatch a duplicate.
|
||||
- **`not_installed`** — Codex CLI absent. Print: "Codex not installed; outside coverage unavailable. Install: `npm install -g @openai/codex`." Keep the required Claude adversarial pass; do not dispatch a duplicate.
|
||||
- **`under_codex`** — stale artifact selected its own harness. Print: "Codex outside review unavailable: harness mismatch; no outside process started. Missing coverage. Repair: setup --host codex." Skip the outside invocation and follow the workflow's native-review instructions below. Conflicting inherited harness markers are not grounds to guess another provider.
|
||||
- **`not_authed`** — installed but no credentials. Print: "Codex not authenticated; outside coverage unavailable. Run `codex login` or set `$CODEX_API_KEY`." Keep the required Claude adversarial pass; do not dispatch a duplicate.
|
||||
|
||||
+41
-15
@@ -18,27 +18,53 @@
|
||||
# - Never change -e / -u / traps / IFS / PATH in the caller shell.
|
||||
# - All internal vars prefix with _GSTACK_CODEX_.
|
||||
# - All functions prefix with _gstack_codex_.
|
||||
# - No command execution at source time (only function defs and the one
|
||||
# self-location assignment below, which is parameter expansion only).
|
||||
# - No command execution at source time (only function defs and the shared
|
||||
# self-location block below, which is parameter expansion only; its
|
||||
# _gstack_helper* variables are the one exception to the prefix rule and
|
||||
# are unset once _GSTACK_CODEX_BIN is set).
|
||||
|
||||
# --- Self-location (bash and zsh) -------------------------------------------
|
||||
# Template blocks source this file into whatever shell the host runs; on macOS
|
||||
# that is zsh (Claude Code's Bash tool included). BASH_SOURCE is bash-only, so
|
||||
# under zsh every "${BASH_SOURCE[0]%/*}/..." path became "/..." and Codex
|
||||
# model selection failed (#3024). zsh names the sourced file with %x; the eval
|
||||
# keeps bash from parsing zsh-only syntax. Resolved once, at source time,
|
||||
# because inside a function zsh's %x names the function, not the file.
|
||||
_GSTACK_CODEX_BIN=""
|
||||
# Under zsh BASH_SOURCE is empty, so every "$dir/../scripts/..." path became
|
||||
# "/..." and Codex model selection failed. The shared block below sets
|
||||
# _gstack_helper_dir; inside a function zsh's %x names the function, not the
|
||||
# file, so it is copied once here.
|
||||
_gstack_helper=gstack-codex-probe
|
||||
# >>> gstack self-locate (shared, byte-identical in every sourced helper) >>>
|
||||
# Skill blocks source helpers into the host's shell: bash, or zsh on macOS
|
||||
# (Claude Code's Bash tool included). Resolved once at source time, using
|
||||
# parameter expansion only: BASH_SOURCE under bash, %x under zsh (inside eval
|
||||
# so bash never parses it), made absolute with $PWD; else $GSTACK_ROOT/bin
|
||||
# when it holds this helper. Any other case, and any other shell, fails loudly
|
||||
# instead of guessing; the message and the return come before any bash- or
|
||||
# zsh-only syntax, so dash and sh print it instead of a syntax error.
|
||||
_gstack_helper_dir=""
|
||||
_gstack_helper_shell=""
|
||||
if [ -n "${BASH_VERSION:-}" ]; then
|
||||
_GSTACK_CODEX_BIN="${BASH_SOURCE[0]}"
|
||||
_gstack_helper_shell=bash
|
||||
_gstack_helper_dir="${BASH_SOURCE[0]}"
|
||||
elif [ -n "${ZSH_VERSION:-}" ]; then
|
||||
eval '_GSTACK_CODEX_BIN="${(%):-%x}"'
|
||||
_gstack_helper_shell=zsh
|
||||
eval '_gstack_helper_dir="${(%):-%x}"'
|
||||
fi
|
||||
case "$_GSTACK_CODEX_BIN" in
|
||||
*/*) _GSTACK_CODEX_BIN="${_GSTACK_CODEX_BIN%/*}" ;;
|
||||
?*) _GSTACK_CODEX_BIN="$PWD" ;;
|
||||
*) _GSTACK_CODEX_BIN="$HOME/.claude/skills/gstack/bin" ;;
|
||||
case "$_gstack_helper_dir" in
|
||||
/*) _gstack_helper_dir="${_gstack_helper_dir%/*}" ;;
|
||||
*/*) _gstack_helper_dir="$PWD/${_gstack_helper_dir%/*}" ;;
|
||||
?*) _gstack_helper_dir="$PWD" ;;
|
||||
esac
|
||||
if [ -n "$_gstack_helper_shell" ] && [ ! -f "$_gstack_helper_dir/$_gstack_helper" ]; then
|
||||
_gstack_helper_dir=""
|
||||
if [ -n "${GSTACK_ROOT:-}" ] && [ -f "$GSTACK_ROOT/bin/$_gstack_helper" ]; then
|
||||
_gstack_helper_dir="$GSTACK_ROOT/bin"
|
||||
fi
|
||||
fi
|
||||
if [ -z "$_gstack_helper_shell" ] || [ -z "$_gstack_helper_dir" ]; then
|
||||
_gstack_helper_error="gstack: cannot locate $_gstack_helper (shell: ${_gstack_helper_shell:-${0##*/}}). Source it from bash or zsh, or export GSTACK_ROOT=<install dir>. https://github.com/garrytan/gstack/blob/main/docs/troubleshooting.md#sourced-helper-location"
|
||||
printf '%s\n' "$_gstack_helper_error" >&2
|
||||
return 1 2>/dev/null || exit 1
|
||||
fi
|
||||
# <<< gstack self-locate <<<
|
||||
_GSTACK_CODEX_BIN="$_gstack_helper_dir"
|
||||
unset _gstack_helper _gstack_helper_dir _gstack_helper_shell
|
||||
|
||||
# --- Auth probe -------------------------------------------------------------
|
||||
|
||||
|
||||
+36
-12
@@ -36,22 +36,46 @@
|
||||
# their own EXIT traps and a trap set by a sourced library would clobber
|
||||
# the caller's. All temp handling is immediate, per call.
|
||||
|
||||
# Self-locate without dirname (builtins only), so the lib works even under
|
||||
# a stripped test PATH. BASH_SOURCE is bash-only; under zsh (the macOS
|
||||
# default, sourced from skill blocks) %x names this file, read through eval
|
||||
# so bash never parses it. Without it the lib resolved to the caller's cwd
|
||||
# and looked for its siblings in the user's project (#3024).
|
||||
_gstack_egress_self=""
|
||||
# Self-locate with builtins only (no dirname), so the lib works under a
|
||||
# stripped test PATH and under zsh, where it used to resolve to the caller's
|
||||
# cwd and look for its siblings in the user's project.
|
||||
_gstack_helper=gstack-egress-lib.sh
|
||||
# >>> gstack self-locate (shared, byte-identical in every sourced helper) >>>
|
||||
# Skill blocks source helpers into the host's shell: bash, or zsh on macOS
|
||||
# (Claude Code's Bash tool included). Resolved once at source time, using
|
||||
# parameter expansion only: BASH_SOURCE under bash, %x under zsh (inside eval
|
||||
# so bash never parses it), made absolute with $PWD; else $GSTACK_ROOT/bin
|
||||
# when it holds this helper. Any other case, and any other shell, fails loudly
|
||||
# instead of guessing; the message and the return come before any bash- or
|
||||
# zsh-only syntax, so dash and sh print it instead of a syntax error.
|
||||
_gstack_helper_dir=""
|
||||
_gstack_helper_shell=""
|
||||
if [ -n "${BASH_VERSION:-}" ]; then
|
||||
_gstack_egress_self="${BASH_SOURCE[0]}"
|
||||
_gstack_helper_shell=bash
|
||||
_gstack_helper_dir="${BASH_SOURCE[0]}"
|
||||
elif [ -n "${ZSH_VERSION:-}" ]; then
|
||||
eval '_gstack_egress_self="${(%):-%x}"'
|
||||
_gstack_helper_shell=zsh
|
||||
eval '_gstack_helper_dir="${(%):-%x}"'
|
||||
fi
|
||||
case "$_gstack_egress_self" in
|
||||
*/*) _gstack_egress_lib_dir="$(cd "${_gstack_egress_self%/*}" && pwd)" ;;
|
||||
*) _gstack_egress_lib_dir="$(pwd)" ;;
|
||||
case "$_gstack_helper_dir" in
|
||||
/*) _gstack_helper_dir="${_gstack_helper_dir%/*}" ;;
|
||||
*/*) _gstack_helper_dir="$PWD/${_gstack_helper_dir%/*}" ;;
|
||||
?*) _gstack_helper_dir="$PWD" ;;
|
||||
esac
|
||||
unset _gstack_egress_self
|
||||
if [ -n "$_gstack_helper_shell" ] && [ ! -f "$_gstack_helper_dir/$_gstack_helper" ]; then
|
||||
_gstack_helper_dir=""
|
||||
if [ -n "${GSTACK_ROOT:-}" ] && [ -f "$GSTACK_ROOT/bin/$_gstack_helper" ]; then
|
||||
_gstack_helper_dir="$GSTACK_ROOT/bin"
|
||||
fi
|
||||
fi
|
||||
if [ -z "$_gstack_helper_shell" ] || [ -z "$_gstack_helper_dir" ]; then
|
||||
_gstack_helper_error="gstack: cannot locate $_gstack_helper (shell: ${_gstack_helper_shell:-${0##*/}}). Source it from bash or zsh, or export GSTACK_ROOT=<install dir>. https://github.com/garrytan/gstack/blob/main/docs/troubleshooting.md#sourced-helper-location"
|
||||
printf '%s\n' "$_gstack_helper_error" >&2
|
||||
return 1 2>/dev/null || exit 1
|
||||
fi
|
||||
# <<< gstack self-locate <<<
|
||||
_gstack_egress_lib_dir="$_gstack_helper_dir"
|
||||
unset _gstack_helper _gstack_helper_dir _gstack_helper_shell
|
||||
|
||||
# State root from the shared twin (bin/gstack-state-root.sh, builtins only),
|
||||
# sourced lazily on first use.
|
||||
|
||||
+5
-1
@@ -476,7 +476,7 @@ invalid or unavailable choice stops with a repair message, never the default.
|
||||
|
||||
```bash
|
||||
_TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || echo off)
|
||||
source ~/.claude/skills/gstack/bin/gstack-codex-probe
|
||||
source ~/.claude/skills/gstack/bin/gstack-codex-probe || { echo "HELPER_UNAVAILABLE"; exit 1; }
|
||||
|
||||
# GSTACK_ACTIVE_HOST names the harness, never the model.
|
||||
if { [ -n "${CODEX_THREAD_ID:-}" ] || [ -n "${CODEX_SANDBOX:-}" ] || [ "${GSTACK_ACTIVE_HOST:-}" = codex ]; }; then
|
||||
@@ -499,6 +499,10 @@ _gstack_codex_version_check # warns if known-bad, non-blocking
|
||||
|
||||
If the runtime guard reports a harness mismatch, stop. Outside coverage is unavailable. Repair with `./setup --host codex`; do not silently substitute another provider or force a same-harness invocation.
|
||||
|
||||
If the output contains `HELPER_UNAVAILABLE`, stop: the gstack helper could not load in
|
||||
this shell. Relay its `gstack: cannot locate ...` line verbatim; it names the shell and
|
||||
links the fix.
|
||||
|
||||
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."
|
||||
|
||||
|
||||
+5
-1
@@ -77,7 +77,7 @@ invalid or unavailable choice stops with a repair message, never the default.
|
||||
|
||||
```bash
|
||||
_TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || echo off)
|
||||
source ~/.claude/skills/gstack/bin/gstack-codex-probe
|
||||
source ~/.claude/skills/gstack/bin/gstack-codex-probe || { echo "HELPER_UNAVAILABLE"; exit 1; }
|
||||
|
||||
{{OUTSIDE_SELF_GUARD:codex}}
|
||||
if ! _gstack_codex_auth_probe >/dev/null; then
|
||||
@@ -91,6 +91,10 @@ _gstack_codex_version_check # warns if known-bad, non-blocking
|
||||
|
||||
If the runtime guard reports a harness mismatch, stop. Outside coverage is unavailable. Repair with `./setup --host codex`; do not silently substitute another provider or force a same-harness invocation.
|
||||
|
||||
If the output contains `HELPER_UNAVAILABLE`, stop: the gstack helper could not load in
|
||||
this shell. Relay its `gstack: cannot locate ...` line verbatim; it names the shell and
|
||||
links the fix.
|
||||
|
||||
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."
|
||||
|
||||
|
||||
@@ -31,6 +31,29 @@ P0 or P1 finding blocks exactly like a native P0/P1. `unverified` and
|
||||
`unavailable` are missing coverage: /ship and /review continue, show the gap in
|
||||
the readiness dashboard and the PR body, and never count it as a pass.
|
||||
|
||||
<a id="sourced-helper-location"></a>
|
||||
### `gstack: cannot locate gstack-codex-probe (shell: ...)` / `CODEX_MODE: helper_unavailable`
|
||||
|
||||
**Meaning.** Skill blocks load gstack's shell helpers (`gstack-codex-probe`,
|
||||
`gstack-egress-lib.sh`) into the shell your agent runs. A helper finds its own
|
||||
directory from bash (`BASH_SOURCE`) or zsh (`%x`). In any other shell (dash,
|
||||
sh), or when the shell cannot say which file it is reading, the helper stops
|
||||
instead of guessing a path. The message names the shell it saw.
|
||||
|
||||
**Fix.** Run the skill from bash or zsh (the macOS and Linux defaults). If
|
||||
the shell cannot be changed, tell the helper where gstack is installed:
|
||||
|
||||
```bash
|
||||
export GSTACK_ROOT=~/.claude/skills/gstack # your install dir; it holds bin/
|
||||
```
|
||||
|
||||
If the message says `cannot load ...`, the helper file is missing: re-run
|
||||
`./setup` from your gstack checkout.
|
||||
|
||||
**Expected result.** `zsh -c 'source ~/.claude/skills/gstack/bin/gstack-codex-probe && _gstack_codex_select_model exec'`
|
||||
prints `CODEX_MODEL: <model> (exec; source: ...)`, and preflights print a
|
||||
`CODEX_MODE` other than `helper_unavailable`.
|
||||
|
||||
<a id="codex-sandbox-unavailable"></a>
|
||||
### `Codex outside review unavailable: Codex's sandbox could not start here (...)`
|
||||
|
||||
|
||||
@@ -225,13 +225,16 @@ health summary and continue to Step 9. Ship-owned children already stopped at St
|
||||
# Codex preflight: one block (functions sourced here don't persist to later blocks).
|
||||
_TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || echo off)
|
||||
_CODEX_CFG=$(~/.claude/skills/gstack/bin/gstack-config get codex_reviews 2>/dev/null || echo enabled)
|
||||
source ~/.claude/skills/gstack/bin/gstack-codex-probe 2>/dev/null || true
|
||||
_gstack_helper_error=""
|
||||
. ~/.claude/skills/gstack/bin/gstack-codex-probe 2>/dev/null || _gstack_helper_error="${_gstack_helper_error:-gstack: cannot load gstack-codex-probe; re-run ./setup. https://github.com/garrytan/gstack/blob/main/docs/troubleshooting.md#sourced-helper-location}"
|
||||
if [ "$_CODEX_CFG" = "disabled" ]; then
|
||||
_CODEX_MODE="disabled"
|
||||
elif { [ -n "${CODEX_THREAD_ID:-}" ] || [ -n "${CODEX_SANDBOX:-}" ] || [ "${GSTACK_ACTIVE_HOST:-}" = codex ]; }; then
|
||||
_CODEX_MODE="under_codex"
|
||||
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 [ -n "$_gstack_helper_error" ]; then
|
||||
_CODEX_MODE="helper_unavailable"; echo "$_gstack_helper_error"
|
||||
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
|
||||
@@ -258,6 +261,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`."
|
||||
- **`helper_unavailable`** — the helper could not load; relay the line above (cause and fix). Fall back to the Claude subagent path.
|
||||
- **`not_installed`** — Codex CLI absent. Print: "Codex not installed; outside coverage unavailable. Install: `npm install -g @openai/codex`." Fall back to the Claude subagent path.
|
||||
- **`under_codex`** — stale artifact selected its own harness. Print: "Codex outside review unavailable: harness mismatch; no outside process started. Missing coverage. Repair: setup --host codex." Skip the outside invocation and follow the workflow's native-review instructions below. Conflicting inherited harness markers are not grounds to guess another provider.
|
||||
- **`not_authed`** — installed but no credentials. Print: "Codex not authenticated; outside coverage unavailable. Run `codex login` or set `$CODEX_API_KEY`." Fall back to the Claude subagent path.
|
||||
|
||||
@@ -68,6 +68,11 @@ export const GATE_OUTCOMES = {
|
||||
summary: "Codex refused the call for this account's usage or rate limit",
|
||||
fix: "wait for the reset time in Codex's message above, or add credits or a higher plan for that account; the model choice is fine",
|
||||
},
|
||||
helper_unavailable: {
|
||||
state: 'unavailable', anchor: 'sourced-helper-location',
|
||||
summary: 'the gstack Codex helper could not be loaded into this shell',
|
||||
fix: 'run the skill from bash or zsh, or export GSTACK_ROOT=<install dir>; if the helper file is missing, re-run ./setup (https://github.com/garrytan/gstack/blob/main/docs/troubleshooting.md#sourced-helper-location)',
|
||||
},
|
||||
probe_inconclusive: {
|
||||
state: 'ran', anchor: 'codex-mode-unverified',
|
||||
summary: 'the Codex model check timed out or hit a network error, so readiness is unverified',
|
||||
|
||||
@@ -360,13 +360,16 @@ review. The user turns this off only by asking explicitly
|
||||
# Codex preflight: one block (functions sourced here don't persist to later blocks).
|
||||
_TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || echo off)
|
||||
_CODEX_CFG=$(~/.claude/skills/gstack/bin/gstack-config get codex_reviews 2>/dev/null || echo enabled)
|
||||
source ~/.claude/skills/gstack/bin/gstack-codex-probe 2>/dev/null || true
|
||||
_gstack_helper_error=""
|
||||
. ~/.claude/skills/gstack/bin/gstack-codex-probe 2>/dev/null || _gstack_helper_error="${_gstack_helper_error:-gstack: cannot load gstack-codex-probe; re-run ./setup. https://github.com/garrytan/gstack/blob/main/docs/troubleshooting.md#sourced-helper-location}"
|
||||
if [ "$_CODEX_CFG" = "disabled" ]; then
|
||||
_CODEX_MODE="disabled"
|
||||
elif { [ -n "${CODEX_THREAD_ID:-}" ] || [ -n "${CODEX_SANDBOX:-}" ] || [ "${GSTACK_ACTIVE_HOST:-}" = codex ]; }; then
|
||||
_CODEX_MODE="under_codex"
|
||||
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 [ -n "$_gstack_helper_error" ]; then
|
||||
_CODEX_MODE="helper_unavailable"; echo "$_gstack_helper_error"
|
||||
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
|
||||
@@ -393,6 +396,7 @@ echo "CODEX_MODE: $_CODEX_MODE"
|
||||
|
||||
Branch on the echoed `CODEX_MODE`:
|
||||
- **`disabled`** — the user turned Codex reviews off (`codex_reviews=disabled`). Skip the reviewer invocation; record disabled coverage as directed below; 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`."
|
||||
- **`helper_unavailable`** — the helper could not load; relay the line above (cause and fix). Fall back to the Claude subagent path.
|
||||
- **`not_installed`** — Codex CLI absent. Print: "Codex not installed; outside coverage unavailable. Install: `npm install -g @openai/codex`." Fall back to the Claude subagent path.
|
||||
- **`under_codex`** — stale artifact selected its own harness. Print: "Codex outside review unavailable: harness mismatch; no outside process started. Missing coverage. Repair: setup --host codex." Skip the outside invocation and construct the prompt below, then follow **Native fallback**. Conflicting inherited harness markers are not grounds to guess another provider.
|
||||
- **`not_authed`** — installed but no credentials. Print: "Codex not authenticated; outside coverage unavailable. Run `codex login` or set `$CODEX_API_KEY`." Fall back to the Claude subagent path.
|
||||
|
||||
@@ -299,13 +299,16 @@ review. The user turns this off only by asking explicitly
|
||||
# Codex preflight: one block (functions sourced here don't persist to later blocks).
|
||||
_TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || echo off)
|
||||
_CODEX_CFG=$(~/.claude/skills/gstack/bin/gstack-config get codex_reviews 2>/dev/null || echo enabled)
|
||||
source ~/.claude/skills/gstack/bin/gstack-codex-probe 2>/dev/null || true
|
||||
_gstack_helper_error=""
|
||||
. ~/.claude/skills/gstack/bin/gstack-codex-probe 2>/dev/null || _gstack_helper_error="${_gstack_helper_error:-gstack: cannot load gstack-codex-probe; re-run ./setup. https://github.com/garrytan/gstack/blob/main/docs/troubleshooting.md#sourced-helper-location}"
|
||||
if [ "$_CODEX_CFG" = "disabled" ]; then
|
||||
_CODEX_MODE="disabled"
|
||||
elif { [ -n "${CODEX_THREAD_ID:-}" ] || [ -n "${CODEX_SANDBOX:-}" ] || [ "${GSTACK_ACTIVE_HOST:-}" = codex ]; }; then
|
||||
_CODEX_MODE="under_codex"
|
||||
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 [ -n "$_gstack_helper_error" ]; then
|
||||
_CODEX_MODE="helper_unavailable"; echo "$_gstack_helper_error"
|
||||
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
|
||||
@@ -332,6 +335,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`."
|
||||
- **`helper_unavailable`** — the helper could not load; relay the line above (cause and fix). Fall back to the Claude subagent path.
|
||||
- **`not_installed`** — Codex CLI absent. Print: "Codex not installed; outside coverage unavailable. Install: `npm install -g @openai/codex`." Fall back to the Claude subagent path.
|
||||
- **`under_codex`** — stale artifact selected its own harness. Print: "Codex outside review unavailable: harness mismatch; no outside process started. Missing coverage. Repair: setup --host codex." Skip the outside invocation and follow the workflow's native-review instructions below. Conflicting inherited harness markers are not grounds to guess another provider.
|
||||
- **`not_authed`** — installed but no credentials. Print: "Codex not authenticated; outside coverage unavailable. Run `codex login` or set `$CODEX_API_KEY`." Fall back to the Claude subagent path.
|
||||
|
||||
@@ -805,13 +805,16 @@ review. The user turns this off only by asking explicitly
|
||||
# Codex preflight: one block (functions sourced here don't persist to later blocks).
|
||||
_TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || echo off)
|
||||
_CODEX_CFG=$(~/.claude/skills/gstack/bin/gstack-config get codex_reviews 2>/dev/null || echo enabled)
|
||||
source ~/.claude/skills/gstack/bin/gstack-codex-probe 2>/dev/null || true
|
||||
_gstack_helper_error=""
|
||||
. ~/.claude/skills/gstack/bin/gstack-codex-probe 2>/dev/null || _gstack_helper_error="${_gstack_helper_error:-gstack: cannot load gstack-codex-probe; re-run ./setup. https://github.com/garrytan/gstack/blob/main/docs/troubleshooting.md#sourced-helper-location}"
|
||||
if [ "$_CODEX_CFG" = "disabled" ]; then
|
||||
_CODEX_MODE="disabled"
|
||||
elif { [ -n "${CODEX_THREAD_ID:-}" ] || [ -n "${CODEX_SANDBOX:-}" ] || [ "${GSTACK_ACTIVE_HOST:-}" = codex ]; }; then
|
||||
_CODEX_MODE="under_codex"
|
||||
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 [ -n "$_gstack_helper_error" ]; then
|
||||
_CODEX_MODE="helper_unavailable"; echo "$_gstack_helper_error"
|
||||
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
|
||||
@@ -838,6 +841,7 @@ echo "CODEX_MODE: $_CODEX_MODE"
|
||||
|
||||
Branch on the echoed `CODEX_MODE`:
|
||||
- **`disabled`** — the user turned Codex reviews off (`codex_reviews=disabled`). Skip the reviewer invocation; record disabled coverage as directed below; 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`."
|
||||
- **`helper_unavailable`** — the helper could not load; relay the line above (cause and fix). Fall back to the Claude subagent path.
|
||||
- **`not_installed`** — Codex CLI absent. Print: "Codex not installed; outside coverage unavailable. Install: `npm install -g @openai/codex`." Fall back to the Claude subagent path.
|
||||
- **`under_codex`** — stale artifact selected its own harness. Print: "Codex outside review unavailable: harness mismatch; no outside process started. Missing coverage. Repair: setup --host codex." Skip the outside invocation and construct the prompt below, then follow **Native fallback**. Conflicting inherited harness markers are not grounds to guess another provider.
|
||||
- **`not_authed`** — installed but no credentials. Print: "Codex not authenticated; outside coverage unavailable. Run `codex login` or set `$CODEX_API_KEY`." Fall back to the Claude subagent path.
|
||||
|
||||
@@ -21,13 +21,16 @@ echo "DIFF_SIZE: $DIFF_TOTAL"
|
||||
# Codex preflight: one block (functions sourced here don't persist to later blocks).
|
||||
_TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || echo off)
|
||||
_CODEX_CFG=$(~/.claude/skills/gstack/bin/gstack-config get codex_reviews 2>/dev/null || echo enabled)
|
||||
source ~/.claude/skills/gstack/bin/gstack-codex-probe 2>/dev/null || true
|
||||
_gstack_helper_error=""
|
||||
. ~/.claude/skills/gstack/bin/gstack-codex-probe 2>/dev/null || _gstack_helper_error="${_gstack_helper_error:-gstack: cannot load gstack-codex-probe; re-run ./setup. https://github.com/garrytan/gstack/blob/main/docs/troubleshooting.md#sourced-helper-location}"
|
||||
if [ "$_CODEX_CFG" = "disabled" ]; then
|
||||
_CODEX_MODE="disabled"
|
||||
elif { [ -n "${CODEX_THREAD_ID:-}" ] || [ -n "${CODEX_SANDBOX:-}" ] || [ "${GSTACK_ACTIVE_HOST:-}" = codex ]; }; then
|
||||
_CODEX_MODE="under_codex"
|
||||
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 [ -n "$_gstack_helper_error" ]; then
|
||||
_CODEX_MODE="helper_unavailable"; echo "$_gstack_helper_error"
|
||||
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
|
||||
@@ -55,6 +58,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."
|
||||
- **`helper_unavailable`** — the helper could not load; relay the line above (cause and fix). Keep the required Claude adversarial pass; do not dispatch a duplicate.
|
||||
- **`not_installed`** — Codex CLI absent. Print: "Codex not installed; outside coverage unavailable. Install: `npm install -g @openai/codex`." Keep the required Claude adversarial pass; do not dispatch a duplicate.
|
||||
- **`under_codex`** — stale artifact selected its own harness. Print: "Codex outside review unavailable: harness mismatch; no outside process started. Missing coverage. Repair: setup --host codex." Skip the outside invocation and follow the workflow's native-review instructions below. Conflicting inherited harness markers are not grounds to guess another provider.
|
||||
- **`not_authed`** — installed but no credentials. Print: "Codex not authenticated; outside coverage unavailable. Run `codex login` or set `$CODEX_API_KEY`." Keep the required Claude adversarial pass; do not dispatch a duplicate.
|
||||
|
||||
@@ -103,15 +103,18 @@ On any error: continue — ${feature} is informational, not a gate.`;
|
||||
* fenced block — CLAUDE.md: each block is a fresh shell, so functions sourced
|
||||
* here do NOT persist to later blocks). It:
|
||||
* 1. reads the `codex_reviews` master switch,
|
||||
* 2. sources `gstack-codex-probe`,
|
||||
* 2. sources `gstack-codex-probe` with POSIX `.`, so even sh/dash reach the
|
||||
* helper's own diagnostic (`CODEX_MODE: helper_unavailable`),
|
||||
* 3. runs `command -v codex` (literal — keeps the e2e substring assertion),
|
||||
* then `_gstack_codex_auth_probe`, then `_gstack_codex_version_check`,
|
||||
* 4. logs the relevant `_gstack_codex_log_event` for each non-ready outcome,
|
||||
* 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` |
|
||||
* `broken_install` | `sandbox_unavailable` | `model_unusable` | `quota_exhausted` | `unverified` | `ready`.
|
||||
* Mode values: `disabled` (config off) | `helper_unavailable` (the probe could
|
||||
* not be sourced; its own diagnostic is echoed) | `not_installed` | `not_authed` |
|
||||
* `broken_install` | `sandbox_unavailable` | `model_unusable` | `quota_exhausted` |
|
||||
* `unverified` | `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.
|
||||
@@ -135,13 +138,16 @@ export function codexPreflight(opts: { modeVar?: string; disabledBehavior: 'skip
|
||||
# Codex preflight: one block (functions sourced here don't persist to later blocks).
|
||||
_TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || echo off)
|
||||
_CODEX_CFG=$(~/.claude/skills/gstack/bin/gstack-config get codex_reviews 2>/dev/null || echo enabled)
|
||||
source ~/.claude/skills/gstack/bin/gstack-codex-probe 2>/dev/null || true
|
||||
_gstack_helper_error=""
|
||||
. ~/.claude/skills/gstack/bin/gstack-codex-probe 2>/dev/null || _gstack_helper_error="\${_gstack_helper_error:-gstack: cannot load gstack-codex-probe; re-run ./setup. https://github.com/garrytan/gstack/blob/main/docs/troubleshooting.md#sourced-helper-location}"
|
||||
if [ "$_CODEX_CFG" = "disabled" ]; then
|
||||
${m}="disabled"
|
||||
elif { [ -n "\${CODEX_THREAD_ID:-}" ] || [ -n "\${CODEX_SANDBOX:-}" ] || [ "\${GSTACK_ACTIVE_HOST:-}" = codex ]; }; then
|
||||
${m}="under_codex"
|
||||
elif ! command -v codex >/dev/null 2>&1; then
|
||||
${m}="not_installed"; _gstack_codex_log_event "codex_cli_missing" 2>/dev/null || true
|
||||
elif [ -n "$_gstack_helper_error" ]; then
|
||||
${m}="helper_unavailable"; echo "$_gstack_helper_error"
|
||||
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
|
||||
@@ -169,6 +175,7 @@ echo "CODEX_MODE: $${m}"
|
||||
|
||||
Branch on the echoed \`CODEX_MODE\`:
|
||||
- **\`disabled\`** — the user turned Codex reviews off (\`codex_reviews=disabled\`). ${disabledLine}
|
||||
- **\`helper_unavailable\`** — the helper could not load; relay the line above (cause and fix). ${nativeRoute}
|
||||
- **\`not_installed\`** — Codex CLI absent. Print: "Codex not installed; outside coverage unavailable. Install: \`npm install -g @openai/codex\`." ${nativeRoute}
|
||||
- **\`under_codex\`** — stale artifact selected its own harness. Print: "Codex outside review unavailable: harness mismatch; no outside process started. Missing coverage. Repair: setup --host codex." Skip the outside invocation and follow the workflow's native-review instructions below. Conflicting inherited harness markers are not grounds to guess another provider.
|
||||
- **\`not_authed\`** — installed but no credentials. Print: "Codex not authenticated; outside coverage unavailable. Run \`codex login\` or set \`$CODEX_API_KEY\`." ${nativeRoute}
|
||||
|
||||
@@ -21,13 +21,16 @@ echo "DIFF_SIZE: $DIFF_TOTAL"
|
||||
# Codex preflight: one block (functions sourced here don't persist to later blocks).
|
||||
_TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || echo off)
|
||||
_CODEX_CFG=$(~/.claude/skills/gstack/bin/gstack-config get codex_reviews 2>/dev/null || echo enabled)
|
||||
source ~/.claude/skills/gstack/bin/gstack-codex-probe 2>/dev/null || true
|
||||
_gstack_helper_error=""
|
||||
. ~/.claude/skills/gstack/bin/gstack-codex-probe 2>/dev/null || _gstack_helper_error="${_gstack_helper_error:-gstack: cannot load gstack-codex-probe; re-run ./setup. https://github.com/garrytan/gstack/blob/main/docs/troubleshooting.md#sourced-helper-location}"
|
||||
if [ "$_CODEX_CFG" = "disabled" ]; then
|
||||
_CODEX_MODE="disabled"
|
||||
elif { [ -n "${CODEX_THREAD_ID:-}" ] || [ -n "${CODEX_SANDBOX:-}" ] || [ "${GSTACK_ACTIVE_HOST:-}" = codex ]; }; then
|
||||
_CODEX_MODE="under_codex"
|
||||
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 [ -n "$_gstack_helper_error" ]; then
|
||||
_CODEX_MODE="helper_unavailable"; echo "$_gstack_helper_error"
|
||||
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
|
||||
@@ -55,6 +58,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."
|
||||
- **`helper_unavailable`** — the helper could not load; relay the line above (cause and fix). Keep the required Claude adversarial pass; do not dispatch a duplicate.
|
||||
- **`not_installed`** — Codex CLI absent. Print: "Codex not installed; outside coverage unavailable. Install: `npm install -g @openai/codex`." Keep the required Claude adversarial pass; do not dispatch a duplicate.
|
||||
- **`under_codex`** — stale artifact selected its own harness. Print: "Codex outside review unavailable: harness mismatch; no outside process started. Missing coverage. Repair: setup --host codex." Skip the outside invocation and follow the workflow's native-review instructions below. Conflicting inherited harness markers are not grounds to guess another provider.
|
||||
- **`not_authed`** — installed but no credentials. Print: "Codex not authenticated; outside coverage unavailable. Run `codex login` or set `$CODEX_API_KEY`." Keep the required Claude adversarial pass; do not dispatch a duplicate.
|
||||
|
||||
@@ -166,7 +166,7 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
|
||||
// wave's headline capability) grows the union to 1.195x. Deliberate:
|
||||
// the section is on-demand (loads only for Apple store targets), so
|
||||
// per-invocation cost for non-iOS ships is one manifest line.
|
||||
maxSizeRatio: 1.432, // Shared advisory identity/dedup + critical-severity validation: 248,065 union bytes / 187,706 baseline = 1.3216 (2026-09-17). + test value bar in the lazy Step 7 section (value cards, weak paths, gate table, base control, machine checks; ~13.6KB): measured 1.396 (2026-09-29). + W1 guarded state-root resolution (`eval gstack-paths; : "${GSTACK_STATE_ROOT:?…}"`) in the Context Recovery preamble, the eureka log and each state-writing bash block; measured 1.401 (2026-09-30). + the shared QA review step's plan-check timing rule (plan checks and their revalidation run on --timeout-ms after smoke expiry); measured 1.4022 (2026-09-30). + the shared TEST_BOOTSTRAP owned-change undo rules (s03 H1-H3, M1) and the value-bar coverage wording (s03 M2/M3); measured 1.4044 (2026-10-02). + severe fix wave ship-review lane: G1 NO_VERSION dispatch, title and CHANGELOG arms (versionless repos ship without inventing a version), F1 parent-side plan binding before the audit dispatch (binding, docs/designs candidates, exact not-run line) and D3's data-not-instructions line; +2,126 union bytes, measured 1.4135 (2026-10-03). + INV-1 consumer lines (outside review `unverified`/`unavailable` is missing coverage in the readiness note and PR body); measured 1.4148 (2026-10-03). + v1.91.19.0 severe fix wave: verdict-form outside-review callers with sandbox preflight and first-use notice (INV-1/B1/Q2), P0/P1 gate and unverified wording, bound-plan discovery (F1), visible LEARNINGS failures (B5), carved external pointers; measured 267,752 / 187,706 = 1.4264 (2026-10-03). + v1.91.18.0 (#3023) merged into the v1.91.19.0 wave; measured 267,874 / 187,706 = 1.4271 (2026-10-04). + Oct 6 fix wave: quota_exhausted preflight branch (#3051) and quoted-heredoc issue template (#3046); measured 268,199 / 187,706 = 1.4288 (2026-10-06). + Step 10's inline no-PR check before the Greptile dispatch (#3020); measured 268,655 / 187,706 = 1.4313 (2026-10-06).
|
||||
maxSizeRatio: 1.434, // Shared advisory identity/dedup + critical-severity validation: 248,065 union bytes / 187,706 baseline = 1.3216 (2026-09-17). + test value bar in the lazy Step 7 section (value cards, weak paths, gate table, base control, machine checks; ~13.6KB): measured 1.396 (2026-09-29). + W1 guarded state-root resolution (`eval gstack-paths; : "${GSTACK_STATE_ROOT:?…}"`) in the Context Recovery preamble, the eureka log and each state-writing bash block; measured 1.401 (2026-09-30). + the shared QA review step's plan-check timing rule (plan checks and their revalidation run on --timeout-ms after smoke expiry); measured 1.4022 (2026-09-30). + the shared TEST_BOOTSTRAP owned-change undo rules (s03 H1-H3, M1) and the value-bar coverage wording (s03 M2/M3); measured 1.4044 (2026-10-02). + severe fix wave ship-review lane: G1 NO_VERSION dispatch, title and CHANGELOG arms (versionless repos ship without inventing a version), F1 parent-side plan binding before the audit dispatch (binding, docs/designs candidates, exact not-run line) and D3's data-not-instructions line; +2,126 union bytes, measured 1.4135 (2026-10-03). + INV-1 consumer lines (outside review `unverified`/`unavailable` is missing coverage in the readiness note and PR body); measured 1.4148 (2026-10-03). + v1.91.19.0 severe fix wave: verdict-form outside-review callers with sandbox preflight and first-use notice (INV-1/B1/Q2), P0/P1 gate and unverified wording, bound-plan discovery (F1), visible LEARNINGS failures (B5), carved external pointers; measured 267,752 / 187,706 = 1.4264 (2026-10-03). + v1.91.18.0 (#3023) merged into the v1.91.19.0 wave; measured 267,874 / 187,706 = 1.4271 (2026-10-04). + Oct 6 fix wave: quota_exhausted preflight branch (#3051) and quoted-heredoc issue template (#3046); measured 268,199 / 187,706 = 1.4288 (2026-10-06). + Step 10's inline no-PR check before the Greptile dispatch (#3020); measured 268,655 / 187,706 = 1.4313 (2026-10-06). + A1 rework: the preflight's helper_unavailable branch (the sourced helper's own diagnostic with its doc URL); measured 269,141 / 187,706 = 1.4338 (2026-10-06).
|
||||
},
|
||||
'plan-ceo-review': {
|
||||
skill: 'plan-ceo-review',
|
||||
@@ -221,7 +221,7 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
|
||||
// 1.08 → 1.10: the scope-gate exceptions block (+ its adversarial-review
|
||||
// hardening: host-anchored mode signal, precedence, passing-mention
|
||||
// guards) and the plan-mode preamble reword land the union at 1.092.
|
||||
maxSizeRatio: 1.175, // + clarity rules for saved decisions/setup gates + the Aside probe's failure reason; measured 1.1504. + test value bar and Tests to Retire in the lazy Test review section (~2.6KB); measured 1.168 + W1 guarded state-root resolution (`eval gstack-paths; : "${GSTACK_STATE_ROOT:?…}"`) in the Context Recovery preamble, the eureka log and each state-writing bash block; measured 1.173 (2026-09-30). + v1.91.12.0 merge of #2999 (headless rule: a disallowed question tool never qualifies) with #3002; measured 1.1741 (2026-10-01).
|
||||
maxSizeRatio: 1.176, // + clarity rules for saved decisions/setup gates + the Aside probe's failure reason; measured 1.1504. + test value bar and Tests to Retire in the lazy Test review section (~2.6KB); measured 1.168 + W1 guarded state-root resolution (`eval gstack-paths; : "${GSTACK_STATE_ROOT:?…}"`) in the Context Recovery preamble, the eureka log and each state-writing bash block; measured 1.173 (2026-09-30). + v1.91.12.0 merge of #2999 (headless rule: a disallowed question tool never qualifies) with #3002; measured 1.1741 (2026-10-01). + Oct 6 fix wave: quota_exhausted and helper_unavailable preflight branches; measured 146,404 / 124,597 = 1.1750 (2026-10-06).
|
||||
},
|
||||
'plan-design-review': {
|
||||
skill: 'plan-design-review',
|
||||
@@ -485,7 +485,7 @@ do not launch the downstream skill or open a browser.`,
|
||||
maxSkeletonBytes: 74_881, // Shared-code identity/skip/action rules + critical-severity validation; measured 74,493 (2026-09-17). + v1.91.12.0 merge of #2999 (review clarity repairs: await reads, research alongside dispatch, /review deadline and setup authority, findings sources) with #3002 (guarded state-root lines, plan-check checkpoints); each fit alone; measured 74,881 (2026-10-01).
|
||||
minUnionBytes: 89_000, // Phase 4 wave 1; measured union 93,357
|
||||
mustContain: ['confidence', 'P1', 'P2', 'Review Army', 'adversarial'],
|
||||
maxSizeRatio: 1.202, // Shared-code feature + critical-severity validation: 128,042 union bytes / 108,523 baseline = 1.1799; preserves content floors. + v1.91.12.0 merge of #2999 (above, plus plan-completion fallback intent and specialist checklist-by-path) with #3002; measured 1.1843 (2026-10-01). + v1.91.19.0 wave: verdict-form Codex callers with sandbox preflight (INV-1/B1), P0/P1 structured gate (B1b), bound-plan discovery and data-not-instructions guard (F1/D3), visible LEARNINGS failures (B5); measured 129,941 / 108,523 = 1.1974 (2026-10-03). + v1.91.18.0 (#3023) merged into the v1.91.19.0 wave; measured 130,025 / 108,523 = 1.1981 (2026-10-04). + Oct 6 fix wave: quota_exhausted preflight branch (#3051); measured 130,359 / 108,523 = 1.2012 (2026-10-06).
|
||||
maxSizeRatio: 1.206, // Shared-code feature + critical-severity validation: 128,042 union bytes / 108,523 baseline = 1.1799; preserves content floors. + v1.91.12.0 merge of #2999 (above, plus plan-completion fallback intent and specialist checklist-by-path) with #3002; measured 1.1843 (2026-10-01). + v1.91.19.0 wave: verdict-form Codex callers with sandbox preflight (INV-1/B1), P0/P1 structured gate (B1b), bound-plan discovery and data-not-instructions guard (F1/D3), visible LEARNINGS failures (B5); measured 129,941 / 108,523 = 1.1974 (2026-10-03). + v1.91.18.0 (#3023) merged into the v1.91.19.0 wave; measured 130,025 / 108,523 = 1.1981 (2026-10-04). + Oct 6 fix wave: quota_exhausted preflight branch (#3051); measured 130,359 / 108,523 = 1.2012 (2026-10-06). + A1 rework: helper_unavailable preflight branch; measured 130,845 / 108,523 = 1.2057 (2026-10-06).
|
||||
},
|
||||
codex: {
|
||||
skill: 'codex',
|
||||
|
||||
@@ -7,20 +7,34 @@
|
||||
* "/../scripts/...".
|
||||
*
|
||||
* Class guard: the helper list is derived from the generated skills and
|
||||
* resolvers (not a hand list), and each helper is sourced under bash and zsh
|
||||
* from an unrelated cwd. CI installs zsh (free-tests.yml), so a missing zsh
|
||||
* there fails instead of skipping.
|
||||
* resolvers, plus every helper those helpers source in turn (not a hand list).
|
||||
* Each helper is sourced under bash and zsh from an unrelated cwd, by absolute
|
||||
* path, by relative path followed by `cd`, and by bare name through PATH, with
|
||||
* one real call each to the model resolver, the auth probe, the state root and
|
||||
* the egress home. Any other shell, or a shell that cannot name the file, gets
|
||||
* the helper's own message instead of a guessed path or a syntax error. CI
|
||||
* installs zsh (free-tests.yml, Dockerfile.ci), so a missing zsh there fails
|
||||
* instead of skipping.
|
||||
*/
|
||||
import { describe, test, expect } from 'bun:test';
|
||||
import { spawnSync } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { codexPreflight } from '../scripts/resolvers/constants';
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
const BIN = path.join(ROOT, 'bin');
|
||||
const PROBE = path.join(BIN, 'gstack-codex-probe');
|
||||
const EGRESS = path.join(BIN, 'gstack-egress-lib.sh');
|
||||
const HAS_ZSH = Boolean(Bun.which('zsh'));
|
||||
const HAS_DASH = Boolean(Bun.which('dash'));
|
||||
const ZSH_REQUIRED = Boolean(process.env.CI) && process.platform !== 'win32';
|
||||
const DASH_REQUIRED = Boolean(process.env.CI) && process.platform === 'linux';
|
||||
const ANCHOR = 'https://github.com/garrytan/gstack/blob/main/docs/troubleshooting.md#sourced-helper-location';
|
||||
const SELF_LOCATE = /# >>> gstack self-locate[^\n]*\n[\s\S]*?# <<< gstack self-locate <<<\n/;
|
||||
|
||||
if (!HAS_ZSH && !ZSH_REQUIRED) console.warn('zsh not installed: install zsh to run the sourced-helper guard (CI requires it)');
|
||||
|
||||
function walk(dir: string, keep: (file: string) => boolean, out: string[] = []): string[] {
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
@@ -32,7 +46,13 @@ function walk(dir: string, keep: (file: string) => boolean, out: string[] = []):
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Every bin/ helper a generated skill or resolver loads with `source` or `.`. */
|
||||
const code = (text: string) => text.split('\n').filter(line => !/^\s*#/.test(line)).join('\n');
|
||||
|
||||
/**
|
||||
* Every bin/ helper a generated skill or resolver loads with `source` or `.`,
|
||||
* plus every bin/ file those helpers source in turn (for example
|
||||
* gstack-state-root.sh, sourced by the codex probe and the egress lib).
|
||||
*/
|
||||
function sourcedHelpers(): string[] {
|
||||
const files = [
|
||||
...walk(path.join(ROOT, 'scripts', 'resolvers'), f => f.endsWith('.ts')),
|
||||
@@ -43,6 +63,7 @@ function sourcedHelpers(): string[] {
|
||||
const names = new Set<string>();
|
||||
const direct = /(?:^|[\s;&|({]|then\s)(?:source|\.)\s+"?[^"\s;)<]*\bbin\/([A-Za-z0-9._-]+)/g;
|
||||
const viaVar = /\/(gstack-[A-Za-z0-9._-]+\.sh)"; \[ -r "\$[A-Z_]+" \] && \. "\$[A-Z_]+"/g;
|
||||
const nested = /(?:^|[\s;&|({]|then\s)(?:source|\.)\s+"?\$\{?[A-Za-z_][A-Za-z0-9_]*\}?\/([A-Za-z0-9._-]+)/g;
|
||||
for (const file of files) {
|
||||
const text = fs.readFileSync(file, 'utf8');
|
||||
for (const re of [direct, viaVar]) {
|
||||
@@ -51,47 +72,93 @@ function sourcedHelpers(): string[] {
|
||||
}
|
||||
}
|
||||
}
|
||||
const queue = [...names];
|
||||
while (queue.length) {
|
||||
const text = code(fs.readFileSync(path.join(BIN, queue.shift()!), 'utf8'));
|
||||
for (const re of [direct, nested]) {
|
||||
for (const m of text.matchAll(re)) {
|
||||
if (!names.has(m[1]!) && fs.existsSync(path.join(BIN, m[1]!))) { names.add(m[1]!); queue.push(m[1]!); }
|
||||
}
|
||||
}
|
||||
}
|
||||
return [...names].sort();
|
||||
}
|
||||
|
||||
function run(shell: 'bash' | 'zsh', script: string, env: Record<string, string> = {}) {
|
||||
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'zsh-helpers-cwd-'));
|
||||
const args = shell === 'zsh' ? ['-f', '-c', script] : ['--noprofile', '--norc', '-c', script];
|
||||
function run(shell: string, script: string, env: Record<string, string> = {}, cwd?: string) {
|
||||
const dir = cwd ?? fs.mkdtempSync(path.join(os.tmpdir(), 'zsh-helpers-cwd-'));
|
||||
const args = shell === 'zsh' ? ['-f', '-c', script] : shell === 'bash' ? ['--noprofile', '--norc', '-c', script] : ['-c', script];
|
||||
const r = spawnSync(shell, args, {
|
||||
cwd,
|
||||
env: { PATH: process.env.PATH ?? '', HOME: cwd, ...env },
|
||||
cwd: dir,
|
||||
env: { PATH: process.env.PATH ?? '', HOME: dir, ...env },
|
||||
encoding: 'utf8',
|
||||
timeout: 20_000,
|
||||
});
|
||||
fs.rmSync(cwd, { recursive: true, force: true });
|
||||
return { code: r.status, out: `${r.stdout ?? ''}${r.stderr ?? ''}` };
|
||||
if (!cwd) fs.rmSync(dir, { recursive: true, force: true });
|
||||
return { code: r.status, stdout: r.stdout ?? '', stderr: r.stderr ?? '', out: `${r.stdout ?? ''}${r.stderr ?? ''}` };
|
||||
}
|
||||
|
||||
/** A HOME whose ~/.claude/skills/gstack is this checkout, a stub codex that answers OK, and Codex credentials. */
|
||||
function preflightHome(install: string = ROOT) {
|
||||
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'zsh-helpers-preflight-'));
|
||||
fs.mkdirSync(path.join(home, '.claude', 'skills'), { recursive: true });
|
||||
fs.symlinkSync(install, path.join(home, '.claude', 'skills', 'gstack'));
|
||||
fs.mkdirSync(path.join(home, 'stub-bin'));
|
||||
fs.writeFileSync(path.join(home, 'stub-bin', 'codex'), '#!/usr/bin/env bash\n[ "$1" = sandbox ] && exit 0\necho OK\n', { mode: 0o755 });
|
||||
fs.mkdirSync(path.join(home, '.codex'));
|
||||
fs.writeFileSync(path.join(home, '.codex', 'config.toml'), 'model = "gpt-test-model"\n');
|
||||
fs.writeFileSync(path.join(home, '.codex', 'auth.json'), '{}');
|
||||
const env = {
|
||||
PATH: `${path.join(home, 'stub-bin')}:${process.env.PATH ?? ''}`,
|
||||
CODEX_HOME: path.join(home, '.codex'),
|
||||
GSTACK_HOME: path.join(home, '.gstack'),
|
||||
};
|
||||
return { home, env };
|
||||
}
|
||||
|
||||
const PREFLIGHT = codexPreflight({ disabledBehavior: 'skip-all' }).match(/```bash\n([\s\S]*?)\n```/)![1]!;
|
||||
const SHELLS = ['bash', 'zsh'] as const;
|
||||
const skipShell = (shell: string) => shell === 'zsh' && !HAS_ZSH && !ZSH_REQUIRED;
|
||||
const skipDash = !HAS_DASH && !DASH_REQUIRED;
|
||||
|
||||
describe('#3024: sourced helpers work under bash and zsh', () => {
|
||||
const helpers = sourcedHelpers();
|
||||
|
||||
test('zsh is present wherever CI runs this suite', () => {
|
||||
test('zsh (and dash on Linux) is present wherever CI runs this suite', () => {
|
||||
if (ZSH_REQUIRED) expect(HAS_ZSH).toBe(true);
|
||||
if (DASH_REQUIRED) expect(HAS_DASH).toBe(true);
|
||||
});
|
||||
|
||||
test('the derived helper list covers the known sourced helpers', () => {
|
||||
for (const known of ['gstack-codex-probe', 'gstack-egress-lib.sh', 'gstack-gbrain-lib.sh']) {
|
||||
test('the derived helper list covers the known sourced helpers, including nested sources', () => {
|
||||
for (const known of ['gstack-codex-probe', 'gstack-egress-lib.sh', 'gstack-gbrain-lib.sh', 'gstack-state-root.sh']) {
|
||||
expect(helpers).toContain(known);
|
||||
}
|
||||
});
|
||||
|
||||
test('no sourced helper uses bash-only self-location or indirection without a zsh branch', () => {
|
||||
test('BASH_SOURCE appears only inside the shared self-locate block, byte for byte in every helper', () => {
|
||||
const blocks = new Map<string, string>();
|
||||
for (const name of helpers) {
|
||||
const text = fs.readFileSync(path.join(BIN, name), 'utf8')
|
||||
.split('\n').filter(line => !/^\s*#/.test(line)).join('\n');
|
||||
if (text.includes('BASH_SOURCE')) {
|
||||
expect({ name, zshBranch: text.includes('ZSH_VERSION') }).toEqual({ name, zshBranch: true });
|
||||
expect({ name, rawUse: /\$\{BASH_SOURCE\[0\]%/.test(text) }).toEqual({ name, rawUse: false });
|
||||
}
|
||||
expect({ name, indirect: /\$\{![A-Za-z_]/.test(text) }).toEqual({ name, indirect: false });
|
||||
const text = fs.readFileSync(path.join(BIN, name), 'utf8');
|
||||
const block = text.match(SELF_LOCATE)?.[0];
|
||||
if (block) blocks.set(name, block);
|
||||
expect({ name, outside: code(text.replace(SELF_LOCATE, '')).includes('BASH_SOURCE') }).toEqual({ name, outside: false });
|
||||
}
|
||||
expect([...blocks.keys()]).toEqual(expect.arrayContaining(['gstack-codex-probe', 'gstack-egress-lib.sh']));
|
||||
const [first] = [...blocks.values()];
|
||||
for (const [name, block] of blocks) expect({ name, block }).toEqual({ name, block: first! });
|
||||
});
|
||||
|
||||
test('no sourced helper uses other bash-only constructs', () => {
|
||||
const bashOnly: Array<[string, RegExp]> = [
|
||||
['${!name} indirection', /\$\{![A-Za-z_]/],
|
||||
['declare -n', /\bdeclare\s+-[A-Za-z]*n/],
|
||||
['mapfile/readarray', /\b(?:mapfile|readarray)\b/],
|
||||
['BASH_REMATCH', /\bBASH_REMATCH\b/],
|
||||
['[[ =~ ]]', /\[\[[^\]\n]*=~/],
|
||||
];
|
||||
for (const name of helpers) {
|
||||
const text = code(fs.readFileSync(path.join(BIN, name), 'utf8'));
|
||||
const found = bashOnly.filter(([, re]) => re.test(text)).map(([label]) => label);
|
||||
expect({ name, found }).toEqual({ name, found: [] });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -107,11 +174,7 @@ describe('#3024: sourced helpers work under bash and zsh', () => {
|
||||
test.skipIf(skipShell(shell))(`codex probe resolves its own directory and selects the model (${shell})`, () => {
|
||||
const codexHome = fs.mkdtempSync(path.join(os.tmpdir(), 'zsh-helpers-codex-'));
|
||||
fs.writeFileSync(path.join(codexHome, 'config.toml'), 'model = "gpt-test-model"\n');
|
||||
const r = run(
|
||||
shell,
|
||||
`. '${path.join(BIN, 'gstack-codex-probe')}' && echo "BIN=$_GSTACK_CODEX_BIN" && _gstack_codex_select_model exec`,
|
||||
{ CODEX_HOME: codexHome },
|
||||
);
|
||||
const r = run(shell, `. '${PROBE}' && echo "BIN=$_GSTACK_CODEX_BIN" && _gstack_codex_select_model exec`, { CODEX_HOME: codexHome });
|
||||
fs.rmSync(codexHome, { recursive: true, force: true });
|
||||
expect(r.out).toContain(`BIN=${BIN}\n`);
|
||||
expect(r.out).toContain('CODEX_MODEL: gpt-test-model (exec;');
|
||||
@@ -119,10 +182,39 @@ describe('#3024: sourced helpers work under bash and zsh', () => {
|
||||
expect(r.code).toBe(0);
|
||||
});
|
||||
|
||||
test.skipIf(skipShell(shell))(`relative-path and bare-name (PATH) sources keep working after cd (${shell})`, () => {
|
||||
// realpath: macOS's temp root is a symlink, and `..` walks the physical path.
|
||||
const cwd = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'zsh-helpers-rel-')));
|
||||
const codexHome = path.join(cwd, 'codex-home');
|
||||
fs.mkdirSync(codexHome);
|
||||
fs.writeFileSync(path.join(codexHome, 'config.toml'), 'model = "gpt-test-model"\n');
|
||||
const env = { CODEX_HOME: codexHome, GSTACK_HOME: path.join(cwd, 'state') };
|
||||
const after = `cd / && _gstack_codex_select_model exec && _gstack_egress_home`;
|
||||
try {
|
||||
for (const [label, load] of [
|
||||
['relative', `. '${path.relative(cwd, PROBE)}' && . '${path.relative(cwd, EGRESS)}'`],
|
||||
['bare name', `PATH='${BIN}':"$PATH" && . gstack-codex-probe && . gstack-egress-lib.sh`],
|
||||
] as const) {
|
||||
const r = run(shell, `${load} && ${after}`, env, cwd);
|
||||
expect({ label, model: r.out.includes('CODEX_MODEL: gpt-test-model (exec;'), home: r.stdout.trim() === env.GSTACK_HOME, code: r.code })
|
||||
.toEqual({ label, model: true, home: true, code: 0 });
|
||||
}
|
||||
} finally { fs.rmSync(cwd, { recursive: true, force: true }); }
|
||||
});
|
||||
|
||||
test.skipIf(skipShell(shell))(`a set (unexported) GSTACK_ROOT locates a helper the shell cannot name; nothing else is guessed (${shell})`, () => {
|
||||
const located = run(shell, `GSTACK_ROOT='${ROOT}'; eval "$(cat '${PROBE}')" && echo "BIN=$_GSTACK_CODEX_BIN"`);
|
||||
expect(located.out).toContain(`BIN=${BIN}\n`);
|
||||
const lost = run(shell, `eval "$(cat '${PROBE}')"; echo "after=$?"`);
|
||||
expect(lost.out).toContain(`gstack: cannot locate gstack-codex-probe (shell: ${shell}). Source it from bash or zsh, or export GSTACK_ROOT=<install dir>. ${ANCHOR}`);
|
||||
expect(lost.out).not.toContain('after=');
|
||||
expect(lost.code).toBe(1);
|
||||
});
|
||||
|
||||
test.skipIf(skipShell(shell))(`codex auth probe reads a custom provider's env_key (${shell})`, () => {
|
||||
const codexHome = fs.mkdtempSync(path.join(os.tmpdir(), 'zsh-helpers-auth-'));
|
||||
fs.writeFileSync(path.join(codexHome, 'config.toml'), '[model_providers.local]\nenv_key = "LOCAL_PROVIDER_KEY"\n');
|
||||
const r = run(shell, `. '${path.join(BIN, 'gstack-codex-probe')}' && _gstack_codex_auth_probe`, {
|
||||
const r = run(shell, `. '${PROBE}' && _gstack_codex_auth_probe`, {
|
||||
CODEX_HOME: codexHome,
|
||||
LOCAL_PROVIDER_KEY: 'present',
|
||||
});
|
||||
@@ -131,9 +223,66 @@ describe('#3024: sourced helpers work under bash and zsh', () => {
|
||||
expect(r.code).toBe(0);
|
||||
});
|
||||
|
||||
test.skipIf(skipShell(shell))(`egress lib resolves its own directory, not the caller's cwd (${shell})`, () => {
|
||||
const r = run(shell, `. '${path.join(BIN, 'gstack-egress-lib.sh')}' && echo "DIR=$_gstack_egress_lib_dir"`);
|
||||
expect(r.out).toContain(`DIR=${BIN}\n`);
|
||||
test.skipIf(skipShell(shell))(`state root and egress home resolve through the helpers, not the caller's cwd (${shell})`, () => {
|
||||
const state = fs.mkdtempSync(path.join(os.tmpdir(), 'zsh-helpers-state-'));
|
||||
try {
|
||||
const root = run(shell, `. '${path.join(BIN, 'gstack-state-root.sh')}' && gstack_state_root`, { GSTACK_HOME: state });
|
||||
expect(root.out.trim()).toBe(state);
|
||||
const egress = run(shell, `. '${EGRESS}' && echo "DIR=$_gstack_egress_lib_dir" && _gstack_egress_home`, { GSTACK_HOME: state });
|
||||
expect(egress.out).toContain(`DIR=${BIN}\n`);
|
||||
expect(egress.stdout).toBe(`DIR=${BIN}\n${state}`);
|
||||
} finally { fs.rmSync(state, { recursive: true, force: true }); }
|
||||
});
|
||||
|
||||
test.skipIf(skipShell(shell))(`the full generated Codex preflight reaches CODEX_MODE: ready (${shell})`, () => {
|
||||
const { home, env } = preflightHome();
|
||||
try {
|
||||
const r = run(shell, PREFLIGHT, env, home);
|
||||
expect(r.out).toContain('CODEX_MODEL: gpt-test-model (exec;');
|
||||
expect(r.out).toContain('CODEX_MODE: ready');
|
||||
} finally { fs.rmSync(home, { recursive: true, force: true }); }
|
||||
});
|
||||
}
|
||||
|
||||
test.skipIf(skipDash)('dash reaches each helper\'s message, never a syntax error, even with GSTACK_ROOT set', () => {
|
||||
for (const name of ['gstack-codex-probe', 'gstack-egress-lib.sh']) {
|
||||
for (const env of [{}, { GSTACK_ROOT: ROOT }] as Record<string, string>[]) {
|
||||
const r = run('dash', `. '${path.join(BIN, name)}'; echo "rc=$?"`, env);
|
||||
expect([r.stderr, r.stdout]).toEqual([`gstack: cannot locate ${name} (shell: dash). Source it from bash or zsh, or export GSTACK_ROOT=<install dir>. ${ANCHOR}\n`, 'rc=1\n']);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('an unknown shell (no BASH_VERSION or ZSH_VERSION) fails loudly instead of guessing', () => {
|
||||
const r = run('bash', `unset BASH_VERSION; . '${PROBE}'; echo "rc=$? bin=\${_GSTACK_CODEX_BIN:-unset}"`, { GSTACK_ROOT: ROOT });
|
||||
expect(r.out).toContain(`gstack: cannot locate gstack-codex-probe (shell: bash). Source it from bash or zsh, or export GSTACK_ROOT=<install dir>. ${ANCHOR}`);
|
||||
expect(r.out).toContain('rc=1 bin=unset');
|
||||
});
|
||||
|
||||
for (const [label, shell, prefix, expected] of [
|
||||
['dash', 'dash', '', `gstack: cannot locate gstack-codex-probe (shell: dash). Source it from bash or zsh, or export GSTACK_ROOT=<install dir>. ${ANCHOR}`],
|
||||
['an unknown shell', 'bash', 'unset BASH_VERSION\n', `gstack: cannot locate gstack-codex-probe (shell: bash). Source it from bash or zsh, or export GSTACK_ROOT=<install dir>. ${ANCHOR}`],
|
||||
] as const) {
|
||||
test.skipIf(shell === 'dash' && skipDash)(`the full generated preflight under ${label} reports helper_unavailable with the helper's line`, () => {
|
||||
const { home, env } = preflightHome();
|
||||
try {
|
||||
const r = run(shell, `${prefix}${PREFLIGHT}`, env, home);
|
||||
expect(r.out).toContain(`${expected}\nCODEX_MODE: helper_unavailable\n`);
|
||||
expect(r.out).not.toContain('not_authed');
|
||||
expect(r.out).not.toMatch(/Syntax error|not found/);
|
||||
} finally { fs.rmSync(home, { recursive: true, force: true }); }
|
||||
});
|
||||
}
|
||||
|
||||
test('the full generated preflight with the helper missing reports helper_unavailable, not not_authed', () => {
|
||||
const empty = fs.mkdtempSync(path.join(os.tmpdir(), 'zsh-helpers-empty-install-'));
|
||||
const { home, env } = preflightHome(empty);
|
||||
try {
|
||||
const r = run('bash', PREFLIGHT, env, home);
|
||||
expect(r.out).toContain(`gstack: cannot load gstack-codex-probe; re-run ./setup. ${ANCHOR}\nCODEX_MODE: helper_unavailable\n`);
|
||||
} finally {
|
||||
fs.rmSync(home, { recursive: true, force: true });
|
||||
fs.rmSync(empty, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user