fix(#2962): enable nullglob in for-glob shell blocks for zsh portability (#3087)

* fix(#2962): enable nullglob in for-glob shell blocks for zsh portability

Workflow shell blocks are fenced bash but execute in the user's login shell
(zsh on macOS). zsh's nomatch default aborts the WHOLE block on an unmatched
glob in a for-list (not just skipping the command), silently bypassing every
statement after it — including the verify-phase decision-coverage gate,
whose optional *-CONTEXT.md lookup used the unsafe for-list form so the
DECISION_RESULT= assignment on the next line never ran under zsh.

Fix: prepend a portable nullglob shim to every bash block containing a
for-glob loop:
  shopt -s nullglob 2>/dev/null; setopt NULL_GLOB 2>/dev/null
Each command no-ops (stderr suppressed) in the shell that doesn't recognize
it; the matching shell enables nullglob so an unmatched glob expands to
nothing and the loop body is skipped cleanly. Verified locally: both zsh and
bash now reach end-of-block (rc=0) on a no-match glob; bash matched-case
behavior unchanged.

14 blocks across 7 files: verify-phase.md (4, incl. the decision-coverage
gate), review.md, execute-phase.md, resume-project.md, complete-milestone.md,
audit-milestone.md, gsd-integration-checker.md, gsd-plan-checker.md (3).
Closes the zsh bypass of the #2770 fix.

* chore(#2962): add changeset fragment

* chore(#2962): backfill changeset PR number 3087

---------

Co-authored-by: sim <sim@local>
This commit is contained in:
Tom Boucher
2026-08-05 15:42:51 -04:00
committed by GitHub
parent 7203011400
commit 589a9b29b0
11 changed files with 49 additions and 10 deletions

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3087
---
**Workflow shell blocks no longer abort under zsh when a glob matches nothing** — an unmatched glob inside a `for` word list aborted the entire shell block under zsh (macOS default shell), silently bypassing every statement after it, including the verify-phase decision-coverage gate. Each affected bash block now enables nullglob portably (`shopt -s nullglob 2>/dev/null; setopt NULL_GLOB 2>/dev/null`) so an unmatched glob expands to nothing and the loop is skipped cleanly under both shells. (#2962)