Skip to content

Commit 8a4afd8

Browse files
garrytanhnshahclaude
authored
fix: zsh glob compatibility in skill preamble (v0.11.7.0) (#386)
* fix(preamble): make .pending-* glob pattern zsh-compatible (fixes #313) **Problem:** When running gstack skills in zsh, users see this error: (eval):22: no matches found: /Users/.../.gstack/analytics/.pending-* **Root Cause:** The Preamble code in gen-skill-docs.ts (line 167) contains: for _PF in ~/.gstack/analytics/.pending-*; do ... In zsh, glob patterns that don't match any files cause an error: 'no matches found: pattern' In bash, the loop simply iterates zero times. This breaks all gstack skills for zsh users (common on macOS). **Solution:** Check if any .pending-* files exist BEFORE attempting the for loop: [ -n "$(ls ~/.gstack/analytics/.pending-* 2>/dev/null)" ] && for ... This approach: - ✅ Works in both bash and zsh - ✅ Silently skips the loop when no pending files exist (normal case) - ✅ Executes the loop when pending files are present - ✅ Uses ls with error suppression (2>/dev/null) for portability **Testing:** - ✅ No pending files: loop skipped, no error - ✅ Pending files exist: loop runs normally - ✅ Compatible with bash and zsh - ✅ TypeScript syntax check passes **Impact:** Fixes all gstack skills for zsh users (macOS default shell). Fixes #313 * test: add zsh glob safety test + regenerate SKILL.md files Adds a test verifying the .pending-* glob in preamble is guarded by an ls check (zsh-compatible). Regenerates all SKILL.md files to propagate the fix from the previous commit. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: regenerate SKILL.md files after merge with main New skills from main (benchmark, autoplan, canary, cso, land-and-deploy, setup-deploy) now include the zsh-compatible .pending-* glob guard. * fix: use find instead of ls for zsh glob safety Codex adversarial review caught that $(ls .pending-* 2>/dev/null) still triggers zsh NOMATCH error because the shell expands the glob before ls runs. Using find avoids shell glob expansion entirely. * chore: bump version and changelog (v0.11.7.0) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: update codex agent skill descriptions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Hiten Shah <hnshah@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0bff8d6 commit 8a4afd8

29 files changed

Lines changed: 92 additions & 46 deletions

File tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
interface:
22
display_name: "gstack-cso"
3-
short_description: "Chief Security Officer mode. Performs OWASP Top 10 audit, STRIDE threat modeling, attack surface analysis, auth flow..."
3+
short_description: "Chief Security Officer mode. Infrastructure-first security audit: secrets archaeology, dependency supply chain,..."
44
default_prompt: "Use gstack-cso for this task."
55
policy:
66
allow_implicit_invocation: true
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
interface:
22
display_name: "gstack"
3-
short_description: "Fast headless browser for QA testing and site dogfooding. Navigate any URL, interact with elements, verify page..."
3+
short_description: "Fast headless browser for QA testing and site dogfooding. Navigate pages, interact with elements, verify state, diff..."
44
default_prompt: "Use gstack for this task."
55
policy:
66
allow_implicit_invocation: true

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Changelog
22

3+
## [0.11.8.0] - 2026-03-23 — zsh Compatibility Fix
4+
5+
### Fixed
6+
7+
- **gstack skills now work in zsh without errors.** Every skill preamble used a `.pending-*` glob pattern that triggered zsh's "no matches found" error on every invocation (the common case where no pending telemetry files exist). Replaced shell glob with `find` to avoid zsh's NOMATCH behavior entirely. Thanks to @hnshah for the initial report and fix in PR #332. Fixes #313.
8+
9+
### Added
10+
11+
- **Regression test for zsh glob safety.** New test verifies all generated SKILL.md files use `find` instead of bare shell globs for `.pending-*` pattern matching.
12+
313
## [0.11.7.0] - 2026-03-23 — /review → /ship Handoff Fix
414

515
### Fixed

SKILL.md

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
name: gstack
3+
version: 1.1.0
34
description: |
45
Fast headless browser for QA testing and site dogfooding. Navigate pages, interact with
56
elements, verify state, diff before/after, take annotated screenshots, test responsive
@@ -13,49 +14,50 @@ description: |
1314
/unfreeze; gstack upgrades /gstack-upgrade. If the user opts out of suggestions, stop
1415
and run gstack-config set proactive false; if they opt back in, run gstack-config set
1516
proactive true.
17+
allowed-tools:
18+
- Bash
19+
- Read
20+
- AskUserQuestion
21+
1622
---
1723
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
1824
<!-- Regenerate: bun run gen:skill-docs -->
1925

2026
## Preamble (run first)
2127

2228
```bash
23-
_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
24-
GSTACK_ROOT="$HOME/.codex/skills/gstack"
25-
[ -n "$_ROOT" ] && [ -d "$_ROOT/.agents/skills/gstack" ] && GSTACK_ROOT="$_ROOT/.agents/skills/gstack"
26-
GSTACK_BIN="$GSTACK_ROOT/bin"
27-
GSTACK_BROWSE="$GSTACK_ROOT/browse/dist"
28-
_UPD=$($GSTACK_BIN/gstack-update-check 2>/dev/null || .agents/skills/gstack/bin/gstack-update-check 2>/dev/null || true)
29+
_UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/skills/gstack/bin/gstack-update-check 2>/dev/null || true)
2930
[ -n "$_UPD" ] && echo "$_UPD" || true
3031
mkdir -p ~/.gstack/sessions
3132
touch ~/.gstack/sessions/"$PPID"
3233
_SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ')
3334
find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true
34-
_CONTRIB=$($GSTACK_BIN/gstack-config get gstack_contributor 2>/dev/null || true)
35-
_PROACTIVE=$($GSTACK_BIN/gstack-config get proactive 2>/dev/null || echo "true")
35+
_CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true)
36+
_PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true")
3637
_BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
3738
echo "BRANCH: $_BRANCH"
3839
echo "PROACTIVE: $_PROACTIVE"
39-
source <($GSTACK_BIN/gstack-repo-mode 2>/dev/null) || true
40+
source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true
4041
REPO_MODE=${REPO_MODE:-unknown}
4142
echo "REPO_MODE: $REPO_MODE"
4243
_LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no")
4344
echo "LAKE_INTRO: $_LAKE_SEEN"
44-
_TEL=$($GSTACK_BIN/gstack-config get telemetry 2>/dev/null || true)
45+
_TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true)
4546
_TEL_PROMPTED=$([ -f ~/.gstack/.telemetry-prompted ] && echo "yes" || echo "no")
4647
_TEL_START=$(date +%s)
4748
_SESSION_ID="$$-$(date +%s)"
4849
echo "TELEMETRY: ${_TEL:-off}"
4950
echo "TEL_PROMPTED: $_TEL_PROMPTED"
5051
mkdir -p ~/.gstack/analytics
5152
echo '{"skill":"gstack","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
52-
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && $GSTACK_BIN/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
53+
# zsh-compatible: use find instead of glob to avoid NOMATCH error
54+
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
5355
```
5456

5557
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
5658
them when the user explicitly asks. The user opted out of proactive suggestions.
5759

58-
If output shows `UPGRADE_AVAILABLE <old> <new>`: read `$GSTACK_ROOT/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED <from> <to>`: tell user "Running gstack v{to} (just updated!)" and continue.
60+
If output shows `UPGRADE_AVAILABLE <old> <new>`: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED <from> <to>`: tell user "Running gstack v{to} (just updated!)" and continue.
5961

6062
If `LAKE_INTRO` is `no`: Before continuing, introduce the Completeness Principle.
6163
Tell the user: "gstack follows the **Boil the Lake** principle — always do the complete
@@ -81,7 +83,7 @@ Options:
8183
- A) Help gstack get better! (recommended)
8284
- B) No thanks
8385

84-
If A: run `$GSTACK_BIN/gstack-config set telemetry community`
86+
If A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry community`
8587

8688
If B: ask a follow-up AskUserQuestion:
8789

@@ -92,8 +94,8 @@ Options:
9294
- A) Sure, anonymous is fine
9395
- B) No thanks, fully off
9496

95-
If B→A: run `$GSTACK_BIN/gstack-config set telemetry anonymous`
96-
If B→B: run `$GSTACK_BIN/gstack-config set telemetry off`
97+
If B→A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry anonymous`
98+
If B→B: run `~/.claude/skills/gstack/bin/gstack-config set telemetry off`
9799

98100
Always run:
99101
```bash
@@ -153,7 +155,7 @@ Never let a noticed issue silently pass. The whole point is proactive communicat
153155

154156
## Search Before Building
155157

156-
Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — **search first.** Read `$GSTACK_ROOT/ETHOS.md` for the full philosophy.
158+
Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — **search first.** Read `~/.claude/skills/gstack/ETHOS.md` for the full philosophy.
157159

158160
**Three layers of knowledge:**
159161
- **Layer 1** (tried and true — in distribution). Don't reinvent the wheel. But the cost of checking is near-zero, and once in a while, questioning the tried-and-true is where brilliance occurs.
@@ -251,7 +253,7 @@ Run this bash:
251253
_TEL_END=$(date +%s)
252254
_TEL_DUR=$(( _TEL_END - _TEL_START ))
253255
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
254-
$GSTACK_ROOT/bin/gstack-telemetry-log \
256+
~/.claude/skills/gstack/bin/gstack-telemetry-log \
255257
--skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \
256258
--used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null &
257259
```
@@ -270,7 +272,7 @@ When you are in plan mode and about to call ExitPlanMode:
270272
3. If it does NOT — run this command:
271273

272274
\`\`\`bash
273-
$GSTACK_ROOT/bin/gstack-review-read
275+
~/.claude/skills/gstack/bin/gstack-review-read
274276
\`\`\`
275277

276278
Then write a `## GSTACK REVIEW REPORT` section to the end of the plan file:
@@ -311,8 +313,8 @@ Auto-shuts down after 30 min idle. State persists between calls (cookies, tabs,
311313
```bash
312314
_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
313315
B=""
314-
[ -n "$_ROOT" ] && [ -x "$_ROOT/.agents/skills/gstack/browse/dist/browse" ] && B="$_ROOT/.agents/skills/gstack/browse/dist/browse"
315-
[ -z "$B" ] && B=$GSTACK_BROWSE/browse
316+
[ -n "$_ROOT" ] && [ -x "$_ROOT/.claude/skills/gstack/browse/dist/browse" ] && B="$_ROOT/.claude/skills/gstack/browse/dist/browse"
317+
[ -z "$B" ] && B=~/.claude/skills/gstack/browse/dist/browse
316318
if [ -x "$B" ]; then
317319
echo "READY: $B"
318320
else

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.11.7.0
1+
0.11.8.0

autoplan/SKILL.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ echo "TELEMETRY: ${_TEL:-off}"
5151
echo "TEL_PROMPTED: $_TEL_PROMPTED"
5252
mkdir -p ~/.gstack/analytics
5353
echo '{"skill":"autoplan","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
54-
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
54+
# zsh-compatible: use find instead of glob to avoid NOMATCH error
55+
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
5556
```
5657

5758
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke

benchmark/SKILL.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ echo "TELEMETRY: ${_TEL:-off}"
4444
echo "TEL_PROMPTED: $_TEL_PROMPTED"
4545
mkdir -p ~/.gstack/analytics
4646
echo '{"skill":"benchmark","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
47-
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
47+
# zsh-compatible: use find instead of glob to avoid NOMATCH error
48+
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
4849
```
4950

5051
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke

browse/SKILL.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ echo "TELEMETRY: ${_TEL:-off}"
4444
echo "TEL_PROMPTED: $_TEL_PROMPTED"
4545
mkdir -p ~/.gstack/analytics
4646
echo '{"skill":"browse","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
47-
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
47+
# zsh-compatible: use find instead of glob to avoid NOMATCH error
48+
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
4849
```
4950

5051
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke

canary/SKILL.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ echo "TELEMETRY: ${_TEL:-off}"
4444
echo "TEL_PROMPTED: $_TEL_PROMPTED"
4545
mkdir -p ~/.gstack/analytics
4646
echo '{"skill":"canary","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
47-
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
47+
# zsh-compatible: use find instead of glob to avoid NOMATCH error
48+
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
4849
```
4950

5051
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke

codex/SKILL.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ echo "TELEMETRY: ${_TEL:-off}"
4545
echo "TEL_PROMPTED: $_TEL_PROMPTED"
4646
mkdir -p ~/.gstack/analytics
4747
echo '{"skill":"codex","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
48-
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
48+
# zsh-compatible: use find instead of glob to avoid NOMATCH error
49+
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
4950
```
5051

5152
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke

0 commit comments

Comments
 (0)