v1.91.16.0 fix: safe installs and upgrades, skills start everywhere, truthful completion (parallel wave) (#3014)

* fix(make-pdf,design): real TOC page numbers and anchors; design models move to gpt-5.5 + gpt-image-2 (#2903, #2807)

* ci: pin every action to a SHA, Scorecard, checksummed Bun, scoped Codex CLI in the CI image, macOS named-regressions job (#1948, #1706, #1997)

* fix(cso,browse): Windows/macOS lease identity, retention skips bad runs, truthful 'not assessed' reports, env.kv false positive, Windows browse spawn fixes (#2894, #2912, #2048, #2637, #2805)

* fix(outside-voice): one Codex model precedence for probe and dispatch, bounded timeouts with KILL escalation, probe cache identity, guarded mktemp, skills hidden in nested Codex (#2776, #2914, #2787, #2881, #2847)

* feat(setup): install registry, ./setup --status, staged activation, per-scope migrations, host tiers and conformance kit, OpenCode/Hermes fixes, Copilot CLI (experimental), disabled_skills, slimmer CLAUDE.md (#1925, #2879, #2347, #2338, #2626, #2629, #2825, #2015, #393, #1206, #2096)

Co-authored-by: Andrey Esipov <61357035+andrey-esipov@users.noreply.github.com>
Co-authored-by: Matthew Williams <5863886+ridermw@users.noreply.github.com>
Co-authored-by: Rocky Wu <92764750+lolisaigao1234@users.noreply.github.com>

* fix(preamble): skills start in worktree-isolated Claude Code sessions — one literal skill-start line, context-recovery helper, no eval in skill blocks, $N token tripwire, honest plan-mode precedence (#2763, #2896, #2851)

* fix(setup): per-install renders for renamed, vendored and custom-config installs, space-safe alias roots, per-host /cso launcher path (#1882, #2906)

* test: free tests never write the real HOME (home-write tripwire), no orphaned browse daemons, symlinked-TMPDIR (macOS) fixes, gbrain-on-PATH isolation (#2895, #2816, #2829, #2841, #2982, #2911, #2909, #2908, #2910)

* chore: integrate wave follow-ups — regenerate all hosts, README model docs, TODOS, touchfiles, duration seeds, Bun 1.3 telemetry test fix, replay fixtures pinned to their recorded bytes

* v1.91.14.0: release metadata

* test(browse): group-kill cleanup falls back to the leader when macOS returns EPERM for a live group

* ci: Codex login steps run under bash (the CI container's default sh rejects pipefail)

* fix: full-suite repairs — private state roots for 7 real-HOME writers, truthful home-guard attribution with --attribute-home, gstack-paths shim in ship-hook fixtures, Copilot in team-init probes, test-of-test detector, cookie review fixture pinned to the approved excerpt

* fix(windows): LF for Dockerfiles, normalized home-guard exclusions, path.relative fixture containment, ship push test spawn timeouts

* test(design-detect): widen the whole-scan budget test's timing margins so a loaded macOS runner can't time out the single-batch call

* fix(evals): qa protocol names defined for the judge, async PTY judge so overlapping sessions keep their cleanup deadline, bws setup cases use the standard JUDGE_MS tier

* test(pty): the non-blocking judge case is POSIX-only (its fake judge is a shebang script, like the PTY harness it covers)

* test(evals): ship-coverage-audit names absolute skill paths, uses Read, and skips the Step 7 artifact nothing consumes (removes the mechanism behind 3 of 4 historical timeouts)

* test: deflake terminal-agent concurrent startup (write the placeholder record before spawning, like the product) and qa-evidence resumed pipe (barrier on a full high-water mark instead of a sleep)

* fix(design): daemon log stream handles open errors instead of crashing the daemon; shutdown test awaits the stubbed exit

* test(evals): skillify checks the product's resolved state root (no GSTACK_HOME bypass), negative cases check every root; tpa shims live outside the agent's cwd and answer consistently

* test(evals): coverage-audit detector splits parallel columns at │ rails and [+] groups; coverage prompts name absolute skill paths; TODOs from the gate eval census

* fix(plan-eng-review): scope gate always sends the menu (no headless 'Scope pending' exit); PTY harness ends idle turn-ends and API-error panels promptly with a precise diagnostic

* test(evals): one position-bound coverage-read rule credits read-only && chains (labels, context ranges, git displays in either order) and still rejects forged output

* fix(ship): documentation section has one saved source inserted by cat (never retyped); spawned-marker evidence is the child output; refreshed codex/factory ship goldens

* test(docsync): the byte-exact insert positive is POSIX-only (the bash-command observer refuses backslash paths by design)

* test(evals): plan-design-with-ui focus-title rule accepts the comma-less 'dimensions or focus' wording (replayed on the captured CI run, negative controls kept)

* fix(qa-callers): exploratory preflight says the scope/method Reads come after exploratory.md; design-review scan check credits an executed scan proven by one engine run over the changed files

---------

Co-authored-by: Andrey Esipov <61357035+andrey-esipov@users.noreply.github.com>
Co-authored-by: Matthew Williams <5863886+ridermw@users.noreply.github.com>
Co-authored-by: Rocky Wu <92764750+lolisaigao1234@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-10-03 07:54:09 -07:00
1 parent 74512c20a7
commit ac20ef1e13
392 files changed
+10217 -3784

No files matched your search

+103 -31
View File
@@ -4,7 +4,8 @@
#
# Functions (all prefixed with _gstack_codex_ for namespace hygiene):
# _gstack_codex_auth_probe — multi-signal auth check (env + file)
# _gstack_codex_model_probe — round-trip probe of gstack's selected model (#2477)
# _gstack_codex_select_model — resolve + print the runtime model (#2914)
# _gstack_codex_model_probe — round-trip probe of the selected model (#2477)
# _gstack_codex_version_check — warn on known-bad Codex CLI versions
# _gstack_codex_timeout_wrapper — gtimeout -> timeout -> bash-native watchdog
# _gstack_codex_log_event — telemetry emission to ~/.gstack/analytics/
@@ -34,24 +35,56 @@ _gstack_codex_auth_probe() {
return 1
}
# --- Runtime model selection (#2914) ---------------------------------------
_gstack_codex_select_model() {
# One selection record per invocation kind, shared by the probe and the
# dispatch flag in the same block: explicit request ($2), GSTACK_CODEX_MODEL,
# Codex config.toml (review_model first for native review), gstack default.
# The TOML is read by the TypeScript resolver; bash never parses it.
local _kind="${1:-exec}" _explicit="${2:-}" _line _rc
case "$_kind" in exec|review) ;; *) echo "ERROR: _gstack_codex_select_model takes exec or review" >&2; return 2 ;; esac
if ! command -v bun >/dev/null 2>&1; then
echo "CODEX_MODEL: unresolved — bun is not on PATH, so gstack cannot read the Codex model choice. Reinstall gstack with ./setup; no Codex call was made." >&2
return 1
fi
if [ -n "$_explicit" ]; then
_line=$(bun run "${BASH_SOURCE[0]%/*}/../scripts/resolve-codex-generation-model.ts" --runtime "$_kind" --explicit "$_explicit")
else
_line=$(bun run "${BASH_SOURCE[0]%/*}/../scripts/resolve-codex-generation-model.ts" --runtime "$_kind")
fi
_rc=$?
_GSTACK_CODEX_SEL="${_line%%$'\t'*}"
_GSTACK_CODEX_SEL_SRC="${_line#*$'\t'}"
case "$_GSTACK_CODEX_SEL" in ''|*[!A-Za-z0-9._:/-]*) _rc=1 ;; esac
if [ "$_rc" -ne 0 ] || [ "${#_GSTACK_CODEX_SEL}" -gt 100 ]; then
_GSTACK_CODEX_SEL=""
_GSTACK_CODEX_SEL_SRC=""
echo "CODEX_MODEL: invalid — Codex outside review unavailable; missing coverage. No Codex call was made. Fix the model choice above; gstack never substitutes its default for it." >&2
return 1
fi
_GSTACK_CODEX_SEL_KIND="$_kind"
echo "CODEX_MODEL: $_GSTACK_CODEX_SEL ($_kind; source: $_GSTACK_CODEX_SEL_SRC)" >&2
}
# --- Model round-trip probe (#2477) ------------------------------------------
_gstack_codex_model_probe() {
# Auth-exists is a weaker signal than the auth probe implies: a ChatGPT
# account can be valid while the model gstack will request is unavailable.
# A short real round trip with gstack's selected model catches model
# rejection and entitlement changes in one shot (#2477). gstack pins a
# frontier default rather than inheriting Codex CLI's built-in default,
# because that default can lag the most capable available model.
# A short real round trip with the selected model catches model rejection
# and entitlement changes in one shot (#2477). The model comes from
# _gstack_codex_select_model ($1 = exec|review, default exec), the same
# record the dispatch flag uses, so the probe never checks a different model.
#
# Contract:
# MODEL_OK (exit 0) — round trip succeeded; cached 1h.
# MODEL_UNUSABLE (exit 1) — deterministic model 400; hints printed.
# Cached 15 min: the 400 is model/entitlement-driven, so re-probing every preflight
# charged the affected user a 30s round trip + real tokens per review
# section, forever. Editing config.toml (the fix) changes the cache
# signature and re-probes immediately; the short TTL covers server-side
# entitlement recovery the signature can't see.
# MODEL_UNUSABLE (exit 1) — deterministic model 400 or an invalid
# model choice; hints printed. The 400 is cached 15 min: re-probing every
# preflight charged the affected user a 30s round trip + real tokens per
# review section, forever. Changing the model or config.toml changes the
# cache signature and re-probes immediately; the short TTL covers
# server-side entitlement recovery the signature can't see.
# MODEL_UNUSABLE_INSTALL (exit 2) — the CLI cannot execute at all (spawn
# ENOENT, non-executable binary, missing vendor payload). Deterministic,
# so fail-open is wrong: retrying never helps. Never cached — a reinstall
@@ -62,28 +95,45 @@ _gstack_codex_model_probe() {
#
# Only call this AFTER _gstack_codex_auth_probe passes — probing without
# auth just measures the auth failure again.
local _kind="${1:-exec}"
if [ -z "${_GSTACK_CODEX_SEL:-}" ] || [ "${_GSTACK_CODEX_SEL_KIND:-}" != "$_kind" ]; then
if ! _gstack_codex_select_model "$_kind"; then
echo "MODEL_UNUSABLE"
echo "HINT: the requested Codex model is not a valid model id; fix it as described above."
return 1
fi
fi
local _model="$_GSTACK_CODEX_SEL"
local _codex_home="${CODEX_HOME:-$HOME/.codex}"
# State root from the shared twin (sourced in a subshell, so the caller's
# shell is untouched). A missing twin only disables the cache.
local _gstack_home _cache=""
_gstack_home="$(. "${BASH_SOURCE[0]%/*}/gstack-state-root.sh" 2>/dev/null && gstack_state_root)" || _gstack_home=""
[ -n "$_gstack_home" ] && _cache="$_gstack_home/.codex-model-probe"
local _model="${GSTACK_CODEX_MODEL:-gpt-6-astra}"
# Cache signature: config.toml + auth.json mtimes + gstack model selection.
# Editing the model env/config or re-logging-in invalidates the cached result
# immediately.
# Cache signature (#2787): everything that can change the round trip's
# outcome without the hour passing — the model, the Codex home and its
# config.toml mtime, the auth mode (which env var or auth.json, plus its
# mtime; never a credential value), and the resolved codex binary and its
# mtime (an upgrade or PATH swap re-probes). Folded through cksum so paths
# with spaces stay one field of the cache line.
# GNU-first stat order + numeric validation (the #2195 pattern): on GNU
# stat, `-f` means FILESYSTEM mode, so the BSD-first form emitted a
# multi-line filesystem block on Linux — the signature then never matched
# its own cache line and the cache missed on every read. BSD stat rejects
# `-c` cleanly, so GNU-first degrades correctly on macOS.
local _cfg_m _auth_m _model_sig _sig
local _cfg_m _auth_m _auth_mode="" _bin _bin_m _sig
_cfg_m=$(stat -c %Y "$_codex_home/config.toml" 2>/dev/null || stat -f %m "$_codex_home/config.toml" 2>/dev/null || echo 0)
_auth_m=$(stat -c %Y "$_codex_home/auth.json" 2>/dev/null || stat -f %m "$_codex_home/auth.json" 2>/dev/null || echo 0)
case "$_cfg_m" in ''|*[!0-9]*) _cfg_m=0 ;; esac
case "$_auth_m" in ''|*[!0-9]*) _auth_m=0 ;; esac
_model_sig=$(printf '%s' "$_model" | sed 's/[^A-Za-z0-9._:-]/_/g')
_sig="${_cfg_m}-${_auth_m}-${_model_sig}"
[ -n "$(printf '%s' "${CODEX_API_KEY:-}" | tr -d '[:space:]')" ] && _auth_mode="${_auth_mode}env-codex,"
[ -n "$(printf '%s' "${OPENAI_API_KEY:-}" | tr -d '[:space:]')" ] && _auth_mode="${_auth_mode}env-openai,"
[ -f "$_codex_home/auth.json" ] && _auth_mode="${_auth_mode}file,"
_bin=$(command -v codex 2>/dev/null || echo none)
_bin=$(readlink -f "$_bin" 2>/dev/null || printf '%s' "$_bin")
_bin_m=$(stat -c %Y "$_bin" 2>/dev/null || stat -f %m "$_bin" 2>/dev/null || echo 0)
case "$_bin_m" in ''|*[!0-9]*) _bin_m=0 ;; esac
_sig=$(printf '%s\n' "$_model" "$_codex_home" "$_cfg_m" "$_auth_mode" "$_auth_m" "$_bin" "$_bin_m" | cksum | tr -d ' ')
local _now
_now=$(date +%s 2>/dev/null || echo 0)
if [ -n "$_cache" ] && [ -f "$_cache" ]; then
@@ -99,13 +149,13 @@ _gstack_codex_model_probe() {
fi
if [ "$_c_status" = "MODEL_UNUSABLE" ] && [ "$_c_sig" = "$_sig" ] && [ $((_now - _c_ts)) -lt 900 ]; then
echo "MODEL_UNUSABLE (cached)"
echo "HINT: gstack requested model '$_model'."
echo "HINT: set GSTACK_CODEX_MODEL=<supported-model> or pass an explicit -c model=... override."
echo "HINT: gstack requested model '$_model' (source: $_GSTACK_CODEX_SEL_SRC)."
echo "HINT: choose a model your account can use: set GSTACK_CODEX_MODEL=<supported-model>, set model in $_codex_home/config.toml, or name one for this request."
return 1
fi
fi
local _out _code
_out=$(_gstack_codex_timeout_wrapper 30 codex exec --skip-git-repo-check -s read-only -c "model=\"$_model\"" "reply OK" </dev/null 2>&1)
_out=$(_gstack_codex_timeout_wrapper 30 codex exec --skip-git-repo-check -s read-only -c "model=\"$_model\"" -c 'skills.include_instructions=false' "reply OK" </dev/null 2>&1)
_code=$?
if [ "$_code" -eq 0 ]; then
[ -n "$_cache" ] && { mkdir -p "$_gstack_home" 2>/dev/null; printf 'MODEL_OK %s %s\n' "$_now" "$_sig" > "$_cache" 2>/dev/null; }
@@ -116,8 +166,8 @@ _gstack_codex_model_probe() {
[ -n "$_cache" ] && { mkdir -p "$_gstack_home" 2>/dev/null; printf 'MODEL_UNUSABLE %s %s\n' "$_now" "$_sig" > "$_cache" 2>/dev/null; }
echo "MODEL_UNUSABLE"
printf '%s\n' "$_out" | grep -i "model" | head -3
echo "HINT: gstack requested model '$_model'."
echo "HINT: set GSTACK_CODEX_MODEL=<supported-model> or pass an explicit -c model=... override."
echo "HINT: gstack requested model '$_model' (source: $_GSTACK_CODEX_SEL_SRC)."
echo "HINT: choose a model your account can use: set GSTACK_CODEX_MODEL=<supported-model>, set model in $_codex_home/config.toml, or name one for this request."
_gstack_codex_log_event "codex_model_unusable" 2>/dev/null || true
return 1
fi
@@ -181,24 +231,46 @@ _gstack_codex_timeout_wrapper() {
# Resolve wrapper binary: prefer gtimeout (Homebrew coreutils on macOS),
# fall back to timeout (Linux), else a bash-native watchdog. Arguments:
# $1 is the duration in seconds; rest is the command to run.
# A child that ignores TERM is KILLed after a grace period
# (_GSTACK_CODEX_KILL_AFTER seconds, default 10), so the deadline always
# ends the provider before the caller's outer tool gate (#2776). Either
# way a deadline reports timeout(1)'s exit 124; output already written stays.
local _duration="$1"
shift
local _to
local _grace="${_GSTACK_CODEX_KILL_AFTER:-10}"
local _to _rc _start
_to=$(command -v gtimeout 2>/dev/null || command -v timeout 2>/dev/null || echo "")
if [ -n "$_to" ]; then
"$_to" "$_duration" "$@"
_start=$(date +%s 2>/dev/null || echo 0)
"$_to" -k "$_grace" "$_duration" "$@"
_rc=$?
# 137 = timeout(1)'s KILL escalation; it is still the deadline firing.
if [ "$_rc" -eq 137 ] && [ $(( $(date +%s 2>/dev/null || echo 0) - _start )) -ge "${_duration%%.*}" ]; then
_rc=124
fi
return "$_rc"
else
# Stock macOS ships neither coreutils gtimeout nor timeout(1); running
# unwrapped let a hung `codex exec` block the probe — and the calling
# workflow — indefinitely. Emulate: background the command, TERM it at
# the deadline, mirror timeout(1)'s exit-124 contract. The watchdog's
# stdout is detached so an early finish never blocks a caller's $(...)
# capture on the orphaned sleep.
# the deadline, KILL it after the grace period if it is still alive, and
# mirror timeout(1)'s exit-124 contract. The watchdog's stdout is
# detached so an early finish never blocks a caller's $(...) capture on
# the orphaned sleep.
"$@" &
local _cmd_pid=$!
( sleep "$_duration" && trap '' TERM && kill -TERM "$_cmd_pid" 2>/dev/null && exit 124 ) >/dev/null 2>&1 &
(
sleep "$_duration" && trap '' TERM && kill -TERM "$_cmd_pid" 2>/dev/null || exit 0
_GSTACK_CODEX_WAITED=0
while [ "$_GSTACK_CODEX_WAITED" -lt "$_grace" ] && kill -0 "$_cmd_pid" 2>/dev/null; do
sleep 1
_GSTACK_CODEX_WAITED=$((_GSTACK_CODEX_WAITED + 1))
done
pkill -KILL -P "$_cmd_pid" 2>/dev/null
kill -KILL "$_cmd_pid" 2>/dev/null
exit 124
) >/dev/null 2>&1 &
local _watch_pid=$!
local _rc
wait "$_cmd_pid"
_rc=$?
kill "$_watch_pid" 2>/dev/null
@@ -244,6 +316,6 @@ _gstack_codex_log_hang() {
local _log_bin="$HOME/.claude/skills/gstack/bin/gstack-learnings-log"
[ -x "$_log_bin" ] || return 0
local _key="codex-hang-$(date +%s 2>/dev/null || echo unknown)"
"$_log_bin" "$(printf '{"skill":"codex","type":"operational","key":"%s","insight":"Codex timed out after 600s during [%s] invocation. Prompt size: %s. Consider splitting prompt or checking network.","confidence":8,"source":"observed","files":["codex/SKILL.md.tmpl","autoplan/SKILL.md.tmpl"]}' "$_key" "$_mode" "$_prompt_size")" \
"$_log_bin" "$(printf '{"skill":"codex","type":"operational","key":"%s","insight":"Codex hit its timeout wrapper during [%s] invocation. Prompt size: %s. Consider splitting prompt or checking network.","confidence":8,"source":"observed","files":["codex/SKILL.md.tmpl","autoplan/SKILL.md.tmpl"]}' "$_key" "$_mode" "$_prompt_size")" \
>/dev/null 2>&1 || true
}