fix(setup): read skill names without a process per skill (#2120)

On Windows on ARM64, x64-emulated Git Bash intermittently deadlocks in MSYS2's
fork() emulation, and ./setup hung in the loops that read every SKILL.md name
through grep | sed | tr plus a basename (about four process starts per skill).
A sourced helper, bin/gstack-skill-name.sh, reads the name with a bash read
loop and no subshell; gstack-patch-names, setup's link and cleanup loops and
gstack-uninstall's inventory use it and parameter expansion instead of
basename. Codex links keep their directory names.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-10-31 18:31:54 +00:00
1 parent c830c9b8ba
commit e19b5e2fd8
11 files changed
+193 -12

No files matched your search

+4 -2
View File
@@ -8,13 +8,15 @@ DO_PREFIX="$2"
# Normalize prefix arg
case "$DO_PREFIX" in true|1) DO_PREFIX=1 ;; *) DO_PREFIX=0 ;; esac
# Names are read with a bash builtin loop: no process per skill (#2120).
. "$(dirname "$0")/gstack-skill-name.sh"
PATCHED=0
for skill_dir in "$GSTACK_DIR"/*/; do
[ -f "$skill_dir/SKILL.md" ] || continue
dir_name="$(basename "$skill_dir")"
dir_name=${skill_dir%/}; dir_name=${dir_name##*/}
[ "$dir_name" = "node_modules" ] && continue
cur=$(grep -m1 '^name:' "$skill_dir/SKILL.md" 2>/dev/null | sed 's/^name:[[:space:]]*//' | tr -d '[:space:]' || true)
gstack_skill_md_name "$skill_dir/SKILL.md"; cur=$GSTACK_SKILL_NAME
[ -z "$cur" ] && continue
[ "$cur" = "gstack" ] && continue # never prefix root skill
if [ "$DO_PREFIX" -eq 1 ]; then
+21
View File
@@ -0,0 +1,21 @@
# shellcheck shell=bash
# gstack-skill-name.sh: sourced helper (bash 3.2). gstack_skill_md_name FILE
# sets GSTACK_SKILL_NAME to the first column-0 "name:" line of FILE with every
# whitespace character removed (CR included), or to "" when there is none or
# FILE cannot be read. Always returns 0.
#
# It starts no process: no subshell, pipe or command substitution, and callers
# must not wrap it in $( ). Setup reads ~55 names per run, and the
# grep | sed | tr pipeline it replaces deadlocked MSYS2's fork() under x64
# emulation on Windows ARM64 (#2120).
gstack_skill_md_name() {
GSTACK_SKILL_NAME=
[ -f "$1" ] && [ -r "$1" ] || return 0
local _gsn_line
while IFS= read -r _gsn_line || [ -n "$_gsn_line" ]; do
case "$_gsn_line" in
name:*) _gsn_line=${_gsn_line#name:}; GSTACK_SKILL_NAME=${_gsn_line//[[:space:]]/}; return 0 ;;
esac
done < "$1"
return 0
}
+5 -2
View File
@@ -59,6 +59,9 @@ GSTACK_DIR="${GSTACK_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
STATE_DIR="$HOME/.gstack"
_STATE_DOC="https://github.com/garrytan/gstack/blob/main/docs/state-root.md"
. "$(dirname "$0")/gstack-state-root.sh" 2>/dev/null || { echo "$0: cannot resolve the gstack state root: $(dirname "$0")/gstack-state-root.sh is missing. fix: reinstall with ./setup or /gstack-upgrade ($_STATE_DOC)" >&2; exit 1; }
# Skill names without a process per skill (#2120); an older copy lacking the helper keeps the pipeline.
. "$(dirname "$0")/gstack-skill-name.sh" 2>/dev/null \
|| gstack_skill_md_name() { GSTACK_SKILL_NAME=$(grep -m1 '^name:' "$1" 2>/dev/null | sed 's/^name:[[:space:]]*//' | tr -d '[:space:]' || true); }
gstack_state_root_select
RESOLVED_ROOT="$_gstack_sr_root"
RESOLVED_VAR="$_gstack_sr_var"
@@ -288,8 +291,8 @@ _INVENTORY=" _gstack-command connect-chrome gstack-connect-chrome "
if [ -d "$GSTACK_DIR" ]; then
for _SRC in "$GSTACK_DIR"/*/; do
[ -f "$_SRC/SKILL.md" ] || continue
_SRC_NAME="$(basename "$_SRC")"
_FM_NAME=$(grep -m1 '^name:' "$_SRC/SKILL.md" 2>/dev/null | sed 's/^name:[[:space:]]*//' | tr -d '[:space:]' || true)
_SRC_NAME=${_SRC%/}; _SRC_NAME=${_SRC_NAME##*/}
gstack_skill_md_name "$_SRC/SKILL.md"; _FM_NAME=$GSTACK_SKILL_NAME
for _N in "$_SRC_NAME" "$_FM_NAME"; do
[ -n "$_N" ] || continue
case "$_INVENTORY" in *" $_N "*) ;; *) _INVENTORY="$_INVENTORY$_N gstack-$_N " ;; esac